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