ruleby 0.8.b2 → 0.8.b4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/core/engine.rb +17 -1
  2. data/lib/core/nodes.rb +126 -94
  3. metadata +3 -3
@@ -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
@@ -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 terminal node
48
+ # variable in the bucket
48
49
  def increment_counter
49
- TerminalNode.increment_counter
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 terminal node
54
+ # variable in the bucket
54
55
  def reset_counter
55
- TerminalNode.reset_counter
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 propagate_retract(fact,out_nodes=@out_nodes)
324
+ end
325
+
326
+ def propagate(propagation_method, object_to_propagate, out_nodes=@out_nodes)
313
327
  out_nodes.each do |out_node|
314
- out_node.retract(fact)
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
- out_nodes.each do |out_node|
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
- out_nodes.each do |out_node|
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
- # If the method does not exist, it is the same as if it evaluted to
399
- # false, and the network traverse stops
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
- # If the method does not exist, it is the same as if it evaluted to
428
- # false, and the network traverse stops
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
- # There is a bug in Ruby MRI that goes away when we call print. Even if the following
435
- # line of code is never executed at runtime. The problem does not exist in JRuby
436
- print ''
437
- raise ProcessInvocationError.new(e), e.message
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
- # If the method does not exist, it is the same as if it evaluted to
474
- # false, and the network traverse stops
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
- @out_nodes.each do |out_node|
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
- @out_nodes.each do |out_node|
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
- @out_nodes.each do |o|
597
- o.retract_resolve(match)
598
- end
623
+ propagate(:retract_resolve, match)
599
624
  end
600
625
 
601
626
  def modify(context)
602
- @out_nodes.each do |out_node|
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
- @out_nodes.each do |out_node|
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
- @out_nodes.each do |out_node|
619
- out_node.retract_right(fact)
620
- end
621
- end
622
-
639
+ propagate(:retract_right, fact)
640
+ end
641
+
623
642
  def retract_resolve(match)
624
- @out_nodes.each do |o|
625
- o.retract_resolve(match)
626
- end
643
+ propagate(:retract_resolve, match)
627
644
  end
628
645
 
629
646
  def modify(context)
630
- @out_nodes.each do |out_node|
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
- @out_nodes.each do |o|
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
- @@counter = 0
835
-
836
- def initialize(rule)
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, @@counter)
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
- def self.reset_counter
893
- @@counter = 0
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
- - b2
9
- version: 0.8.b2
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-07 00:00:00 -05:00
18
+ date: 2011-04-12 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies: []
21
21