ruleby 0.8.b7 → 0.8.b8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/core/atoms.rb +26 -2
- data/lib/core/nodes.rb +138 -72
- data/lib/dsl/ferrari.rb +15 -2
- data/lib/rule_helper.rb +17 -1
- metadata +1 -1
data/lib/core/atoms.rb
CHANGED
@@ -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)
|
data/lib/core/nodes.rb
CHANGED
@@ -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
|
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
|
220
|
-
|
221
|
-
|
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
|
311
|
-
|
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(
|
323
|
-
propagate_retract(
|
316
|
+
def retract(assertable)
|
317
|
+
propagate_retract(assertable)
|
324
318
|
end
|
325
319
|
|
326
|
-
def propagate(propagation_method,
|
327
|
-
out_nodes.each do |out_node|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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(
|
337
|
-
propagate(:retract,
|
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
|
-
|
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
|
-
|
401
|
-
if
|
402
|
-
@values[k] = [
|
403
|
-
|
404
|
-
@values[k]
|
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(
|
409
|
-
propagate_retract
|
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(
|
413
|
-
k = fact.object.send(@atom.method)
|
414
|
-
|
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(
|
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(
|
515
|
-
propagate_assert
|
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
|
540
|
-
|
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
|
-
#
|
597
|
+
# TODO once we fix up TypeNode, we won't need this
|
548
598
|
mr[atom.tag] = fact.object
|
549
|
-
|
550
|
-
|
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
|
-
|
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(
|
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
|
data/lib/dsl/ferrari.rb
CHANGED
@@ -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
|
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
|
data/lib/rule_helper.rb
CHANGED
@@ -37,7 +37,23 @@ module Ruleby
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def c(&block)
|
40
|
-
|
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)
|