ruleby 0.8.b7 → 0.8.b8

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.
@@ -45,8 +45,32 @@ module Ruleby
45
45
  @deftemplate == atom.deftemplate &&
46
46
  @proc == atom.proc
47
47
  end
48
- end
49
-
48
+ end
49
+
50
+ class FunctionAtom < Atom
51
+
52
+ attr_reader :arguments
53
+
54
+ def initialize(tag, template, arguments, block)
55
+ @tag = tag
56
+ @method = nil
57
+ @deftemplate = template
58
+ @arguments = arguments
59
+ @proc = block
60
+ end
61
+
62
+ def shareable?(atom)
63
+ FunctionAtom === atom &&
64
+ @deftemplate == atom.deftemplate &&
65
+ @arguments == atom.arguments &&
66
+ @proc == atom.proc
67
+ end
68
+
69
+ def to_s
70
+ return "#{self.class},#{@deftemplate},#{@arguments.inspect}"
71
+ end
72
+ end
73
+
50
74
  # TODO use this
51
75
  class BlockAtom < PropertyAtom
52
76
  def shareable?(atom)
@@ -132,30 +132,26 @@ module Ruleby
132
132
  def create_atom_nodes(pattern, out_node, side)
133
133
  # TODO refactor this method so it clear and concise
134
134
  type_node = create_type_node(pattern)
135
- forked = false
136
135
  parent_atom = pattern.atoms[0]
137
136
  parent_node = type_node
138
137
 
139
138
  pattern.atoms[1..-1].each do |atom|
140
- # If the network has been forked, we don't want to share nodes anymore
141
- forked = true if parent_node.forks?(parent_atom)
142
-
143
139
  if atom.kind_of?(SelfReferenceAtom)
144
140
  node = create_self_reference_node(atom)
145
141
  elsif atom.kind_of?(ReferenceAtom)
146
142
  node = create_reference_node(atom)
147
143
  out_node.ref_nodes.push node
148
144
  else
149
- node = create_property_node(atom,forked)
145
+ node = create_property_node(atom)
150
146
  end
151
- parent_node.add_out_node node, parent_atom
147
+ parent_node.add_out_node node, pattern.hash, parent_atom
152
148
  node.parent_nodes.push parent_node
153
149
  parent_node = node
154
150
  parent_atom = atom
155
151
  end
156
152
 
157
153
  bridge_node = create_bridge_node(pattern)
158
- parent_node.add_out_node bridge_node, parent_atom
154
+ parent_node.add_out_node bridge_node, pattern.hash, parent_atom
159
155
  bridge_node.parent_nodes.push parent_node
160
156
  parent_node = bridge_node
161
157
 
@@ -163,11 +159,11 @@ module Ruleby
163
159
 
164
160
  if out_node.kind_of?(JoinNode)
165
161
  adapter_node = create_adapter_node(side)
166
- parent_node.add_out_node adapter_node
162
+ parent_node.add_out_node adapter_node, pattern.hash
167
163
  parent_node = adapter_node
168
164
  end
169
165
 
170
- parent_node.add_out_node out_node
166
+ parent_node.add_out_node out_node, pattern.hash
171
167
  compare_to_wm(type_node)
172
168
  return type_node
173
169
  end
@@ -188,10 +184,10 @@ module Ruleby
188
184
  parent_node = join_node
189
185
  if out_node.kind_of?(JoinNode)
190
186
  adapter_node = create_adapter_node(side)
191
- parent_node.add_out_node adapter_node
187
+ parent_node.add_out_node adapter_node, pattern.hash
192
188
  parent_node = adapter_node
193
189
  end
194
- parent_node.add_out_node out_node
190
+ parent_node.add_out_node out_node, pattern.hash
195
191
  return join_node
196
192
  end
197
193
 
@@ -216,9 +212,15 @@ module Ruleby
216
212
  end
217
213
  end
218
214
 
219
- def create_property_node(atom,forked)
220
- node = atom.kind_of?(EqualsAtom) ? EqualsNode.new(@bucket, atom) : PropertyNode.new(@bucket, atom)
221
- @atom_nodes.each {|n| return n if n.shareable? node} unless forked
215
+ def create_property_node(atom)
216
+ if atom.kind_of?(EqualsAtom)
217
+ node = EqualsNode.new(@bucket, atom)
218
+ elsif atom.kind_of?(FunctionAtom)
219
+ node = FunctionNode.new(@bucket, atom)
220
+ else
221
+ node = PropertyNode.new(@bucket, atom)
222
+ end
223
+ @atom_nodes.each {|n| return n if n.shareable? node}
222
224
  @atom_nodes.push node
223
225
  return node
224
226
  end
@@ -303,38 +305,41 @@ module Ruleby
303
305
  attr_reader :child_nodes
304
306
  def initialize(bucket)
305
307
  super
306
- @out_nodes = []
308
+ @out_nodes = {}
307
309
  end
308
310
 
309
- def add_out_node(node,atom=nil)
310
- unless @out_nodes.index node
311
- @out_nodes.push node
312
- end
313
- end
314
-
315
- # returns true if this node is already being used for the same atom. That
316
- # is, if it is used again it will fork the network (or the network may
317
- # already be forked).
318
- def forks?(atom)
319
- return !@out_nodes.empty?
311
+ def add_out_node(node, path, atom=nil)
312
+ @out_nodes[node] = [] unless @out_nodes[node]
313
+ @out_nodes[node] << path
320
314
  end
321
315
 
322
- def retract(fact)
323
- propagate_retract(fact)
316
+ def retract(assertable)
317
+ propagate_retract(assertable)
324
318
  end
325
319
 
326
- def propagate(propagation_method, object_to_propagate, out_nodes=@out_nodes)
327
- out_nodes.each do |out_node|
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})
320
+ def propagate(propagation_method, assertable, out_nodes=@out_nodes)
321
+ out_nodes.each do |out_node, paths|
322
+ if assertable.respond_to?(:paths)
323
+ continuing_paths = paths & assertable.paths
324
+ unless continuing_paths.empty?
325
+ begin
326
+ out_node.send(propagation_method, Assertion.new(assertable.fact, continuing_paths))
327
+ rescue => e
328
+ @bucket.add_error Error.new(:unknown, :error, {:type => e.class, :message => e.message})
329
+ end
330
+ end
331
+ else
332
+ begin
333
+ out_node.send(propagation_method, assertable)
334
+ rescue => e
335
+ @bucket.add_error Error.new(:unknown, :error, {:type => e.class, :message => e.message})
336
+ end
332
337
  end
333
338
  end
334
339
  end
335
340
 
336
- def propagate_retract(fact,out_nodes=@out_nodes)
337
- propagate(:retract, fact, out_nodes)
341
+ def propagate_retract(assertable,out_nodes=@out_nodes)
342
+ propagate(:retract, assertable, out_nodes)
338
343
  end
339
344
 
340
345
  def assert(assertable)
@@ -384,37 +389,31 @@ module Ruleby
384
389
  def initialize(bucket, atom)
385
390
  super
386
391
  @values = {}
387
- @values.default = []
388
392
  end
389
393
 
390
- # returns true if this node is already being used for the same atom. That
391
- # is, if it is used again it will fork the network (or the network may
392
- # already be forked).
393
- def forks?(atom)
394
- k = hash_by(atom)
395
- return !@values[k].empty?
396
- end
397
-
398
- def add_out_node(node,atom)
394
+ def add_out_node(node, path, atom)
399
395
  k = hash_by(atom)
400
- v = @values[k]
401
- if v.empty?
402
- @values[k] = [node]
403
- elsif !v.index node
404
- @values[k] = v << node
396
+ @values[k] = {} if @values[k].nil?
397
+ if @values[k][node].nil?
398
+ @values[k][node] = [path]
399
+ else
400
+ @values[k][node] << path
405
401
  end
406
402
  end
407
403
 
408
- def retract(fact)
409
- propagate_retract fact, @values.values.flatten
404
+ def retract(assertable)
405
+ propagate_retract assertable, @values.values.inject({}){|p,c| p.merge(c)} #(@values[k] ? @values[k] : {})
410
406
  end
411
407
 
412
- def assert(fact)
413
- k = fact.object.send(@atom.method)
414
- propagate_assert fact, @values[k]
408
+ def assert(assertable)
409
+ k = assertable.fact.object.send(@atom.method)
410
+
411
+ # TODOwe need to do this for ALL tags if this node is shared
412
+ assertable.add_tag(@atom.tag, k)
413
+ propagate_assert assertable, (@values[k] ? @values[k] : {})
415
414
  rescue NoMethodError => e
416
415
  @bucket.add_error Error.new(:no_method, :warn, {
417
- :object => fact.object.to_s,
416
+ :object => assertable.fact.object.to_s,
418
417
  :method => e.name,
419
418
  :message => e.message
420
419
  })
@@ -427,6 +426,32 @@ module Ruleby
427
426
  def hash_by(atom)
428
427
  atom.deftemplate.clazz
429
428
  end
429
+
430
+ def retract(fact)
431
+ propagate_retract fact, @values.values.inject({}){|p,c| p.merge(c)} #(@values[k] ? @values[k] : {})
432
+ end
433
+
434
+ def assert(fact)
435
+ k = fact.object.send(@atom.method)
436
+ # TODO we should create the Assertion object here, not in propogate
437
+ propagate_assert fact, (@values[k] ? @values[k] : {})
438
+ rescue NoMethodError => e
439
+ @bucket.add_error Error.new(:no_method, :warn, {
440
+ :object => fact.object.to_s,
441
+ :method => e.name,
442
+ :message => e.message
443
+ })
444
+ end
445
+
446
+ def propagate(propagation_method, fact, out_nodes=@out_nodes)
447
+ out_nodes.each do |out_node, paths|
448
+ begin
449
+ out_node.send(propagation_method, Assertion.new(fact, paths))
450
+ rescue => e
451
+ @bucket.add_error Error.new(:unknown, :error, {:type => e.class, :message => e.message})
452
+ end
453
+ end
454
+ end
430
455
  end
431
456
 
432
457
  # This class is used for the same purpose as the TypeNode, but it matches
@@ -441,12 +466,13 @@ module Ruleby
441
466
 
442
467
  # This node class is used for matching properties of a fact.
443
468
  class PropertyNode < AtomNode
444
- def assert(fact)
469
+ def assert(assertable)
445
470
  begin
446
- val = fact.object.send(@atom.method)
471
+ val = assertable.fact.object.send(@atom.method)
472
+ assertable.add_tag(@atom.tag, val)
447
473
  rescue NoMethodError => e
448
474
  @bucket.add_error Error.new(:no_method, :warn, {
449
- :object => fact.object.to_s,
475
+ :object => assertable.fact.object.to_s,
450
476
  :method => e.name,
451
477
  :message => e.message
452
478
  })
@@ -456,7 +482,7 @@ module Ruleby
456
482
  super if @atom.proc.call(val)
457
483
  rescue Exception => e
458
484
  @bucket.add_error Error.new(:proc_call, :error, {
459
- :object => fact.object.to_s,
485
+ :object => assertable.fact.object.to_s,
460
486
  :method => @atom.method,
461
487
  :value => val.to_s,
462
488
  :message => e.message
@@ -474,6 +500,22 @@ module Ruleby
474
500
  end
475
501
  end
476
502
 
503
+ # This node class is used for matching properties of a fact.
504
+ class FunctionNode < AtomNode
505
+ def assert(assertable)
506
+ begin
507
+ super if @atom.proc.call(assertable.fact.object, *@atom.arguments)
508
+ rescue Exception => e
509
+ @bucket.add_error Error.new(:proc_call, :error, {
510
+ :object => fact.object.to_s,
511
+ :function => true,
512
+ :arguments => @atom.arguments,
513
+ :message => e.message
514
+ })
515
+ end
516
+ end
517
+ end
518
+
477
519
  # This node class is used to match properties of one with the properties
478
520
  # of any other already matched fact. It differs from the other AtomNodes
479
521
  # because it does not perform any inline matching. The match method is only
@@ -511,8 +553,8 @@ module Ruleby
511
553
  # This node class is used to match properties of a fact with other properties
512
554
  # of itself. Unlike ReferenceAtom it does perform inline matching.
513
555
  class SelfReferenceNode < AtomNode
514
- def assert(fact)
515
- propagate_assert fact if match fact
556
+ def assert(assertable)
557
+ propagate_assert assertable if match assertable.fact
516
558
  end
517
559
 
518
560
  def match(fact)
@@ -533,24 +575,36 @@ module Ruleby
533
575
  super(bucket)
534
576
  @pattern = pattern
535
577
  end
578
+
579
+ def assert(assertable)
580
+ propagate_assert(assertable.fact)
581
+ end
582
+
583
+ def retract(assertable)
584
+ propagate_retract(assertable.fact)
585
+ end
536
586
  end
537
587
 
538
588
  class BridgeNode < BaseBridgeNode
539
- def propagate_assert(fact)
540
- # create the partial match
541
- mr = MatchResult.new
542
- mr.is_match = true
589
+ def assert(assertable)
590
+ fact = assertable.fact
591
+ mr = MatchResult.new(assertable.tags)
592
+ mr.is_match = true
543
593
  mr.recency.push fact.recency
544
594
  @pattern.atoms.each do |atom|
545
595
  mr.fact_hash[atom.tag] = fact.id
546
596
  if atom == @pattern.head
547
- # HACK its a pain to have to check for this, can we make it special
597
+ # TODO once we fix up TypeNode, we won't need this
548
598
  mr[atom.tag] = fact.object
549
- else
550
- mr[atom.tag] = fact.object.send(atom.method)
599
+ elsif !mr.key?(atom.tag) and atom.method
600
+ # this is a big hack for the sake of performance. should really do whats described below
601
+ unless atom.tag.is_a?(GeneratedTag)
602
+ # TODO it should be possible to get rid of this, and just capture it in the Nodes above
603
+ mr[atom.tag] = fact.object.send(atom.method)
604
+ end
551
605
  end
552
606
  end
553
- super(MatchContext.new(fact,mr))
607
+ propagate_assert(MatchContext.new(fact,mr))
554
608
  end
555
609
  end
556
610
 
@@ -565,7 +619,8 @@ module Ruleby
565
619
  # @collection_memory.recency = 0
566
620
  end
567
621
 
568
- def retract(fact)
622
+ def retract(assertable)
623
+ fact = assertable.fact
569
624
  propagate_retract(@collection_memory)
570
625
  propagate_assert(fact) do
571
626
  @collection_memory.object.delete_if {|a| a == fact.object}
@@ -935,5 +990,16 @@ module Ruleby
935
990
  end
936
991
  end
937
992
 
993
+ class Assertion < Struct.new(:fact, :paths)
994
+ def add_tag(tag, value)
995
+ @tags ||= {}
996
+ @tags[tag] = value
997
+ end
998
+
999
+ def tags
1000
+ @tags ? @tags : {}
1001
+ end
1002
+ end
1003
+
938
1004
  end
939
1005
  end
@@ -257,6 +257,8 @@ module Ruleby
257
257
  arg.deftemplate = deftemplate
258
258
  @methods[arg.tag] = arg.name
259
259
  atoms.push *arg.build_atoms(@tags, @methods, @when_counter)
260
+ elsif arg.kind_of? FunctionBuilder
261
+ atoms.push arg.build_atom(GeneratedTag.new, deftemplate)
260
262
  elsif arg == false
261
263
  raise 'The != operator is not allowed.'
262
264
  else
@@ -317,8 +319,19 @@ module Ruleby
317
319
  return ab
318
320
  end
319
321
  end
320
-
321
- class BindingBuilder
322
+
323
+ class FunctionBuilder
324
+ def initialize(args, block)
325
+ @args = args
326
+ @function = block
327
+ end
328
+
329
+ def build_atom(tag, template)
330
+ Core::FunctionAtom.new(tag, template, @args, @function)
331
+ end
332
+ end
333
+
334
+ class BindingBuilder
322
335
  attr_accessor :tag, :method
323
336
  def initialize(tag,method=nil)
324
337
  @tag = tag
@@ -37,7 +37,23 @@ module Ruleby
37
37
  end
38
38
 
39
39
  def c(&block)
40
- return lambda(&block)
40
+ lambda(&block)
41
+ end
42
+
43
+ def f(args, block=nil)
44
+ if block.nil?
45
+ if !args.is_a?(Proc)
46
+ raise "You must provide a Proc!"
47
+ else
48
+ Ruleby::Ferrari::FunctionBuilder.new([], args)
49
+ end
50
+ else
51
+ if args.is_a?(Array)
52
+ Ruleby::Ferrari::FunctionBuilder.new(args, block)
53
+ else
54
+ Ruleby::Ferrari::FunctionBuilder.new([args], block)
55
+ end
56
+ end
41
57
  end
42
58
 
43
59
  def OR(*args)
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ruleby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: 4
5
- version: 0.8.b7
5
+ version: 0.8.b8
6
6
  platform: ruby
7
7
  authors:
8
8
  - Joe Kutner