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.
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