ruleby 0.8.b7 → 0.8.b8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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