ruleby 0.8.b9 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/core/atoms.rb +50 -47
- data/lib/core/nodes.rb +26 -26
- data/lib/dsl/ferrari.rb +35 -21
- data/lib/rulebook.rb +2 -9
- metadata +5 -7
- data/lib/dsl/letigre.rb +0 -238
- data/lib/dsl/steel.rb +0 -314
data/lib/core/atoms.rb
CHANGED
@@ -14,17 +14,17 @@ module Ruleby
|
|
14
14
|
module Core
|
15
15
|
|
16
16
|
class Atom
|
17
|
-
attr_reader :tag, :proc, :
|
17
|
+
attr_reader :tag, :proc, :slot, :template
|
18
18
|
|
19
|
-
def initialize(tag,
|
19
|
+
def initialize(tag, slot, template, block)
|
20
20
|
@tag = tag
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@proc =
|
21
|
+
@slot = slot
|
22
|
+
@template = template
|
23
|
+
@proc = block
|
24
24
|
end
|
25
25
|
|
26
26
|
def to_s
|
27
|
-
|
27
|
+
"#{self.class},#{@tag},#{@slot},#{@template}"
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -34,16 +34,25 @@ module Ruleby
|
|
34
34
|
# a.person{ |p| p.is_a? Person }
|
35
35
|
#
|
36
36
|
# So there are no references to other atoms.
|
37
|
-
class PropertyAtom < Atom
|
37
|
+
class PropertyAtom < Atom
|
38
|
+
|
39
|
+
attr_reader :value
|
40
|
+
|
41
|
+
def initialize(tag, slot, template, value, block)
|
42
|
+
super(tag,slot,template, block)
|
43
|
+
@value = value
|
44
|
+
end
|
45
|
+
|
38
46
|
def ==(atom)
|
39
|
-
|
47
|
+
shareable?(atom) && @tag == atom.tag
|
40
48
|
end
|
41
49
|
|
42
50
|
def shareable?(atom)
|
43
|
-
|
44
|
-
@
|
45
|
-
@
|
46
|
-
@proc == atom.proc
|
51
|
+
PropertyAtom === atom &&
|
52
|
+
@slot == atom.slot &&
|
53
|
+
@template == atom.template &&
|
54
|
+
@proc == atom.proc &&
|
55
|
+
@value == atom.value
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
@@ -53,30 +62,21 @@ module Ruleby
|
|
53
62
|
|
54
63
|
def initialize(tag, template, arguments, block)
|
55
64
|
@tag = tag
|
56
|
-
@
|
57
|
-
@
|
65
|
+
@slot = nil
|
66
|
+
@template = template
|
58
67
|
@arguments = arguments
|
59
68
|
@proc = block
|
60
69
|
end
|
61
70
|
|
62
71
|
def shareable?(atom)
|
63
72
|
FunctionAtom === atom &&
|
64
|
-
@
|
73
|
+
@template == atom.template &&
|
65
74
|
@arguments == atom.arguments &&
|
66
75
|
@proc == atom.proc
|
67
76
|
end
|
68
77
|
|
69
78
|
def to_s
|
70
|
-
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# TODO use this
|
75
|
-
class BlockAtom < PropertyAtom
|
76
|
-
def shareable?(atom)
|
77
|
-
return super &&
|
78
|
-
BlockAtom === atom &&
|
79
|
-
@proc == atom.proc
|
79
|
+
"#{self.class},#{@template},#{@arguments.inspect}"
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -87,16 +87,16 @@ module Ruleby
|
|
87
87
|
#
|
88
88
|
# So there are no references to other atoms.
|
89
89
|
class EqualsAtom < PropertyAtom
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
EQUAL_PROC = lambda {|x, y| x == y}
|
91
|
+
|
92
|
+
def initialize(tag, slot, template, value)
|
93
|
+
super(tag,slot,template, value, EQUAL_PROC)
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
def shareable?(atom)
|
97
|
-
|
98
|
-
@
|
99
|
-
@
|
97
|
+
EqualsAtom === atom &&
|
98
|
+
@slot == atom.slot &&
|
99
|
+
@template == atom.template
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
@@ -105,17 +105,20 @@ module Ruleby
|
|
105
105
|
# 'For each Person as :p'
|
106
106
|
#
|
107
107
|
# It is only used at the start of a pattern.
|
108
|
-
class HeadAtom <
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
108
|
+
class HeadAtom < PropertyAtom
|
109
|
+
HEAD_EQUAL_PROC = lambda {|t, c| t == c}
|
110
|
+
HEAD_INHERITS_PROC = lambda {|t, c| t === c}
|
111
|
+
|
112
|
+
def initialize(tag, template)
|
113
|
+
if template.mode == :equals
|
114
|
+
super tag, :class, template, template.clazz, HEAD_EQUAL_PROC
|
115
|
+
elsif template.mode == :inherits
|
116
|
+
super tag, :class, template, template.clazz, HEAD_INHERITS_PROC
|
114
117
|
end
|
115
118
|
end
|
116
119
|
|
117
120
|
def shareable?(atom)
|
118
|
-
|
121
|
+
HeadAtom === atom && @template == atom.template
|
119
122
|
end
|
120
123
|
end
|
121
124
|
|
@@ -128,8 +131,8 @@ module Ruleby
|
|
128
131
|
class ReferenceAtom < Atom
|
129
132
|
attr_reader :vars
|
130
133
|
|
131
|
-
def initialize(tag,
|
132
|
-
super(tag,
|
134
|
+
def initialize(tag, slot, vars, template, block)
|
135
|
+
super(tag, slot, template, block)
|
133
136
|
@vars = vars # list of referenced variable names
|
134
137
|
end
|
135
138
|
|
@@ -138,21 +141,21 @@ module Ruleby
|
|
138
141
|
end
|
139
142
|
|
140
143
|
def ==(atom)
|
141
|
-
|
144
|
+
ReferenceAtom === atom &&
|
142
145
|
@proc == atom.proc &&
|
143
146
|
@tag == atom.tag &&
|
144
147
|
@vars == atom.vars &&
|
145
|
-
@
|
148
|
+
@template == atom.template
|
146
149
|
end
|
147
150
|
|
148
151
|
def to_s
|
149
|
-
|
152
|
+
super + ", vars=#{vars.join(',')}"
|
150
153
|
end
|
151
154
|
end
|
152
155
|
|
153
156
|
# This is an atom that references another atom that is in the same pattern.
|
154
157
|
# Note that in a SelfReferenceAtom, the 'vars' argument must be a list of the
|
155
|
-
# *
|
158
|
+
# *slots* that this atom references (not the variable names)!
|
156
159
|
class SelfReferenceAtom < ReferenceAtom
|
157
160
|
end
|
158
161
|
|
@@ -179,4 +182,4 @@ module Ruleby
|
|
179
182
|
end
|
180
183
|
|
181
184
|
end
|
182
|
-
end
|
185
|
+
end
|
data/lib/core/nodes.rb
CHANGED
@@ -222,27 +222,23 @@ module Ruleby
|
|
222
222
|
end
|
223
223
|
@atom_nodes.each {|n| return n if n.shareable? node}
|
224
224
|
@atom_nodes.push node
|
225
|
-
|
225
|
+
node
|
226
226
|
end
|
227
227
|
|
228
228
|
def create_self_reference_node(atom)
|
229
229
|
node = SelfReferenceNode.new(@bucket, atom)
|
230
230
|
@atom_nodes.push node
|
231
|
-
|
231
|
+
node
|
232
232
|
end
|
233
233
|
|
234
234
|
def create_reference_node(atom)
|
235
235
|
node = ReferenceNode.new(@bucket, atom)
|
236
236
|
@atom_nodes.push node
|
237
|
-
|
237
|
+
node
|
238
238
|
end
|
239
239
|
|
240
240
|
def create_adapter_node(side)
|
241
|
-
|
242
|
-
return LeftAdapterNode.new(@bucket)
|
243
|
-
else
|
244
|
-
return RightAdapterNode.new(@bucket)
|
245
|
-
end
|
241
|
+
side == :left ? LeftAdapterNode.new(@bucket) : RightAdapterNode.new(@bucket)
|
246
242
|
end
|
247
243
|
|
248
244
|
# This method is used to update each TypeNode based on the facts in
|
@@ -294,7 +290,7 @@ module Ruleby
|
|
294
290
|
end
|
295
291
|
end
|
296
292
|
end
|
297
|
-
|
293
|
+
true
|
298
294
|
end
|
299
295
|
end
|
300
296
|
|
@@ -370,15 +366,15 @@ module Ruleby
|
|
370
366
|
end
|
371
367
|
|
372
368
|
def ==(node)
|
373
|
-
|
369
|
+
AtomNode === node && @atom == node.atom
|
374
370
|
end
|
375
371
|
|
376
372
|
def shareable?(node)
|
377
|
-
|
373
|
+
@atom.shareable?(node.atom)
|
378
374
|
end
|
379
375
|
|
380
376
|
def to_s
|
381
|
-
super + " - #{@atom.
|
377
|
+
super + " - #{@atom.slot}"
|
382
378
|
end
|
383
379
|
end
|
384
380
|
|
@@ -406,7 +402,7 @@ module Ruleby
|
|
406
402
|
end
|
407
403
|
|
408
404
|
def assert(assertable)
|
409
|
-
k = assertable.fact.object.send(@atom.
|
405
|
+
k = assertable.fact.object.send(@atom.slot)
|
410
406
|
|
411
407
|
# TODOwe need to do this for ALL tags if this node is shared
|
412
408
|
assertable.add_tag(@atom.tag, k)
|
@@ -424,7 +420,7 @@ module Ruleby
|
|
424
420
|
# is matched exactly (ignoring inheritance).
|
425
421
|
class TypeNode < HashedNode
|
426
422
|
def hash_by(atom)
|
427
|
-
atom.
|
423
|
+
atom.template.clazz
|
428
424
|
end
|
429
425
|
|
430
426
|
def retract(fact)
|
@@ -432,7 +428,7 @@ module Ruleby
|
|
432
428
|
end
|
433
429
|
|
434
430
|
def assert(fact)
|
435
|
-
k = fact.object.send(@atom.
|
431
|
+
k = fact.object.send(@atom.slot)
|
436
432
|
# TODO we should create the Assertion object here, not in propogate
|
437
433
|
propagate_assert fact, (@values[k] ? @values[k] : {})
|
438
434
|
rescue NoMethodError => e
|
@@ -468,8 +464,8 @@ module Ruleby
|
|
468
464
|
class PropertyNode < AtomNode
|
469
465
|
def assert(assertable)
|
470
466
|
begin
|
471
|
-
|
472
|
-
assertable.add_tag(@atom.tag,
|
467
|
+
v = assertable.fact.object.send(@atom.slot)
|
468
|
+
assertable.add_tag(@atom.tag, v)
|
473
469
|
rescue NoMethodError => e
|
474
470
|
@bucket.add_error Error.new(:no_method, :warn, {
|
475
471
|
:object => assertable.fact.object.to_s,
|
@@ -479,12 +475,16 @@ module Ruleby
|
|
479
475
|
return
|
480
476
|
end
|
481
477
|
begin
|
482
|
-
|
478
|
+
if @atom.proc.arity == 1
|
479
|
+
super if @atom.proc.call(v)
|
480
|
+
else
|
481
|
+
super if @atom.proc.call(v, @atom.value)
|
482
|
+
end
|
483
483
|
rescue Exception => e
|
484
484
|
@bucket.add_error Error.new(:proc_call, :error, {
|
485
485
|
:object => assertable.fact.object.to_s,
|
486
|
-
:method => @atom.
|
487
|
-
:value =>
|
486
|
+
:method => @atom.slot,
|
487
|
+
:value => v.to_s,
|
488
488
|
:message => e.message
|
489
489
|
})
|
490
490
|
end
|
@@ -500,7 +500,7 @@ module Ruleby
|
|
500
500
|
end
|
501
501
|
end
|
502
502
|
|
503
|
-
# This node class is used
|
503
|
+
# This node class is used conditions that are simply a function, which returns true or false.
|
504
504
|
class FunctionNode < AtomNode
|
505
505
|
def assert(assertable)
|
506
506
|
begin
|
@@ -522,7 +522,7 @@ module Ruleby
|
|
522
522
|
# invoked by the two input node.
|
523
523
|
class ReferenceNode < AtomNode
|
524
524
|
def match(left_context,right_fact)
|
525
|
-
val = right_fact.object.send(@atom.
|
525
|
+
val = right_fact.object.send(@atom.slot)
|
526
526
|
args = [val]
|
527
527
|
match = left_context.match
|
528
528
|
@atom.vars.each do |var|
|
@@ -558,7 +558,7 @@ module Ruleby
|
|
558
558
|
end
|
559
559
|
|
560
560
|
def match(fact)
|
561
|
-
args = [fact.object.send(@atom.
|
561
|
+
args = [fact.object.send(@atom.slot)]
|
562
562
|
@atom.vars.each do |var|
|
563
563
|
args.push fact.object.send(var)
|
564
564
|
end
|
@@ -596,11 +596,11 @@ module Ruleby
|
|
596
596
|
if atom == @pattern.head
|
597
597
|
# TODO once we fix up TypeNode, we won't need this
|
598
598
|
mr[atom.tag] = fact.object
|
599
|
-
elsif !mr.key?(atom.tag) and atom.
|
599
|
+
elsif !mr.key?(atom.tag) and atom.slot
|
600
600
|
# this is a big hack for the sake of performance. should really do whats described below
|
601
601
|
unless atom.tag.is_a?(GeneratedTag)
|
602
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.
|
603
|
+
mr[atom.tag] = fact.object.send(atom.slot)
|
604
604
|
end
|
605
605
|
end
|
606
606
|
end
|
@@ -1002,4 +1002,4 @@ module Ruleby
|
|
1002
1002
|
end
|
1003
1003
|
|
1004
1004
|
end
|
1005
|
-
end
|
1005
|
+
end
|
data/lib/dsl/ferrari.rb
CHANGED
@@ -301,7 +301,7 @@ module Ruleby
|
|
301
301
|
public_instance_methods.each do |m|
|
302
302
|
# maybe we shouldn't be undefing object_id. What are the implications? Can we make object_id a
|
303
303
|
# pass through to the underlying object's object_id?
|
304
|
-
a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
|
304
|
+
a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__, :object_id]
|
305
305
|
undef_method m.to_sym unless a.include? m.to_sym
|
306
306
|
end
|
307
307
|
|
@@ -316,7 +316,7 @@ module Ruleby
|
|
316
316
|
puts args.class.to_s + ' --- ' + args.to_s
|
317
317
|
raise 'Arguments not supported for short-hand conditions'
|
318
318
|
end
|
319
|
-
|
319
|
+
ab
|
320
320
|
end
|
321
321
|
end
|
322
322
|
|
@@ -361,46 +361,59 @@ module Ruleby
|
|
361
361
|
|
362
362
|
class AtomBuilder
|
363
363
|
attr_accessor :tag, :name, :bindings, :deftemplate, :block
|
364
|
-
|
364
|
+
|
365
|
+
EQ_PROC = lambda {|x,y| x and x == y}
|
366
|
+
GT_PROC = lambda {|x,y| x and x > y}
|
367
|
+
LT_PROC = lambda {|x,y| x and x < y}
|
368
|
+
MATCH_PROC = lambda {|x,y| x and x =~ y}
|
369
|
+
LTE_PROC = lambda {|x,y| x and x <= y}
|
370
|
+
GTE_PROC = lambda {|x,y| x and x >= y}
|
371
|
+
TRUE_PROC = lambda {|x| true}
|
372
|
+
|
365
373
|
def initialize(method_id)
|
366
374
|
@name = method_id
|
367
375
|
@deftemplate = nil
|
368
376
|
@tag = GeneratedTag.new
|
369
377
|
@bindings = []
|
370
|
-
@block =
|
378
|
+
@block = TRUE_PROC
|
371
379
|
@child_atom_builders = []
|
372
380
|
end
|
373
381
|
|
374
382
|
def method_missing(method_id, *args, &block)
|
375
383
|
if method_id == :not
|
376
|
-
|
384
|
+
NotOperatorBuilder.new(@name)
|
377
385
|
end
|
378
386
|
end
|
379
387
|
|
380
388
|
def ==(value)
|
381
389
|
@atom_type = :equals
|
382
|
-
|
383
|
-
|
390
|
+
create_block value, EQ_PROC
|
391
|
+
self
|
384
392
|
end
|
385
393
|
|
386
394
|
def >(value)
|
387
|
-
create_block value,
|
395
|
+
create_block value, GT_PROC
|
396
|
+
self
|
388
397
|
end
|
389
398
|
|
390
399
|
def <(value)
|
391
|
-
create_block value,
|
400
|
+
create_block value, LT_PROC
|
401
|
+
self
|
392
402
|
end
|
393
403
|
|
394
404
|
def =~(value)
|
395
|
-
create_block value,
|
405
|
+
create_block value, MATCH_PROC
|
406
|
+
self
|
396
407
|
end
|
397
408
|
|
398
409
|
def <=(value)
|
399
|
-
create_block value,
|
410
|
+
create_block value, LTE_PROC
|
411
|
+
self
|
400
412
|
end
|
401
413
|
|
402
414
|
def >=(value)
|
403
|
-
create_block value,
|
415
|
+
create_block value, GTE_PROC
|
416
|
+
self
|
404
417
|
end
|
405
418
|
|
406
419
|
def build_atoms(tags,methods,when_id)
|
@@ -414,16 +427,16 @@ module Ruleby
|
|
414
427
|
if @atom_type == :equals
|
415
428
|
return atoms << Core::EqualsAtom.new(@tag, @name, @deftemplate, @value)
|
416
429
|
else
|
417
|
-
return atoms << Core::PropertyAtom.new(@tag, @name, @deftemplate,
|
430
|
+
return atoms << Core::PropertyAtom.new(@tag, @name, @deftemplate, @value, @block)
|
418
431
|
end
|
419
432
|
end
|
420
433
|
|
421
434
|
if references_self?(tags,when_id)
|
422
435
|
bind_methods = @bindings.collect{ |bb| methods[bb.tag] }
|
423
|
-
atoms << Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate
|
436
|
+
atoms << Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate,@block)
|
424
437
|
else
|
425
438
|
bind_tags = @bindings.collect{ |bb| bb.tag }
|
426
|
-
atoms << Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate
|
439
|
+
atoms << Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate,@block)
|
427
440
|
end
|
428
441
|
end
|
429
442
|
|
@@ -440,26 +453,27 @@ module Ruleby
|
|
440
453
|
raise 'Binding to self and another pattern in the same condition is not yet supported.'
|
441
454
|
end
|
442
455
|
|
443
|
-
|
456
|
+
ref_self > 0
|
444
457
|
end
|
445
458
|
|
446
|
-
def create_block(value,
|
459
|
+
def create_block(value, block)
|
460
|
+
@block = block
|
447
461
|
if value && value.kind_of?(BindingBuilder)
|
448
462
|
@bindings = [value]
|
449
|
-
@block = ref_block
|
450
463
|
elsif value && value.kind_of?(AtomBuilder)
|
451
464
|
@child_atom_builders << value
|
452
465
|
@bindings = [BindingBuilder.new(value.tag)]
|
453
|
-
@block = ref_block
|
454
466
|
else
|
455
|
-
@
|
467
|
+
@value = value
|
456
468
|
end
|
457
469
|
end
|
458
470
|
end
|
459
471
|
|
460
472
|
class NotOperatorBuilder < AtomBuilder
|
473
|
+
NOT_PROC = lambda {|x,y| x != y}
|
461
474
|
def ==(value)
|
462
|
-
create_block value,
|
475
|
+
create_block value, NOT_PROC
|
476
|
+
self
|
463
477
|
end
|
464
478
|
end
|
465
479
|
|
data/lib/rulebook.rb
CHANGED
@@ -12,8 +12,6 @@
|
|
12
12
|
require 'ruleby'
|
13
13
|
require 'rule_helper'
|
14
14
|
require 'dsl/ferrari'
|
15
|
-
require 'dsl/letigre'
|
16
|
-
require 'dsl/steel'
|
17
15
|
|
18
16
|
module Ruleby
|
19
17
|
class Rulebook
|
@@ -36,15 +34,10 @@ module Ruleby
|
|
36
34
|
@engine.modify fact
|
37
35
|
end
|
38
36
|
def rule(*args, &block)
|
39
|
-
unless args.empty?
|
40
|
-
name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
|
41
|
-
end
|
42
|
-
|
43
37
|
if args.empty?
|
44
|
-
|
45
|
-
r = Steel::RulebookHelper.new @engine
|
46
|
-
r.rule name, &block
|
38
|
+
raise 'Must provide arguments to rule'
|
47
39
|
else
|
40
|
+
name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
|
48
41
|
i = args[0].kind_of?(Hash) ? 1 : 0
|
49
42
|
if [Array, Ruleby::Ferrari::OrBuilder, Ruleby::Ferrari::AndBuilder].include? args[i].class
|
50
43
|
# use ferrari DSL
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruleby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
version: 0.8
|
4
|
+
prerelease:
|
5
|
+
version: "0.8"
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Joe Kutner
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-06-
|
14
|
+
date: 2011-06-16 00:00:00 -05:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|
@@ -38,8 +38,6 @@ files:
|
|
38
38
|
- lib/core/patterns.rb
|
39
39
|
- lib/core/utils.rb
|
40
40
|
- lib/dsl/ferrari.rb
|
41
|
-
- lib/dsl/letigre.rb
|
42
|
-
- lib/dsl/steel.rb
|
43
41
|
- tests/test.rb
|
44
42
|
has_rdoc: true
|
45
43
|
homepage: http://ruleby.org
|
@@ -59,9 +57,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
57
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
58
|
none: false
|
61
59
|
requirements:
|
62
|
-
- - "
|
60
|
+
- - ">="
|
63
61
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
62
|
+
version: "0"
|
65
63
|
requirements: []
|
66
64
|
|
67
65
|
rubyforge_project: ruleby
|
data/lib/dsl/letigre.rb
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Joe Kutner
|
10
|
-
#
|
11
|
-
|
12
|
-
module Ruleby
|
13
|
-
module LeTigre
|
14
|
-
class RulebookHelper
|
15
|
-
|
16
|
-
def initialize(engine, rulebook)
|
17
|
-
@rulebook = rulebook
|
18
|
-
@engine = engine
|
19
|
-
end
|
20
|
-
|
21
|
-
attr_reader :engine
|
22
|
-
|
23
|
-
def rule(name, *args, &then_block)
|
24
|
-
if args.empty?
|
25
|
-
raise 'No conditions supplied.'
|
26
|
-
end
|
27
|
-
|
28
|
-
options = args[0].kind_of?(Hash) ? args.shift : {}
|
29
|
-
|
30
|
-
pb = PatternParser.new @rulebook
|
31
|
-
pattern = pb.parse args
|
32
|
-
|
33
|
-
rb = RuleBuilder.new name
|
34
|
-
|
35
|
-
rb.when(pattern)
|
36
|
-
rb.then(&then_block)
|
37
|
-
rb.priority = options[:priority] if options[:priority]
|
38
|
-
|
39
|
-
@engine.assert_rule rb.build_rule
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
class RuleBuilder
|
45
|
-
|
46
|
-
def initialize(name, pattern=nil, action=nil, priority=0)
|
47
|
-
@name = name
|
48
|
-
@pattern = pattern
|
49
|
-
@action = action
|
50
|
-
@priority = priority
|
51
|
-
|
52
|
-
@tags = {}
|
53
|
-
@when_counter = 0
|
54
|
-
end
|
55
|
-
|
56
|
-
def when(pattern)
|
57
|
-
@pattern = pattern
|
58
|
-
end
|
59
|
-
|
60
|
-
def then(&block)
|
61
|
-
@action = Core::Action.new(&block)
|
62
|
-
@action.name = @name
|
63
|
-
@action.priority = @priority
|
64
|
-
end
|
65
|
-
|
66
|
-
def priority
|
67
|
-
return @priority
|
68
|
-
end
|
69
|
-
|
70
|
-
def priority=(p)
|
71
|
-
@priority = p
|
72
|
-
@action.priority = @priority
|
73
|
-
end
|
74
|
-
|
75
|
-
def build_rule
|
76
|
-
Core::Rule.new @name, @pattern, @action, @priority
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class PatternParser
|
81
|
-
@@head_error = 'Invalid type specification.'
|
82
|
-
@@method_error = "No #method in expression: "
|
83
|
-
|
84
|
-
@@base_re = /(For each|\w*\s*exists\??|not\??)(.*)/
|
85
|
-
@@mode_re = /( (is a|kind of|instance of) )(.*)/
|
86
|
-
@@where_re = /(.*)where (.*)/
|
87
|
-
@@head_re = /(\w*)( as :(.*))?/
|
88
|
-
|
89
|
-
@@method_re = /#((\w|\d|\_)*)\??/
|
90
|
-
@@bind_re = /#:(\w*|\d*\_*)*/
|
91
|
-
@@and_re = /#&&/
|
92
|
-
@@tag_re = /(.*) as :(.*)/
|
93
|
-
|
94
|
-
def initialize(rulebook)
|
95
|
-
@rulebook = rulebook
|
96
|
-
end
|
97
|
-
|
98
|
-
def parse(lhs_strs)
|
99
|
-
pattern = nil
|
100
|
-
lhs_strs.each do |lhs|
|
101
|
-
# match the quantifier
|
102
|
-
if lhs =~ @@base_re
|
103
|
-
base = $1
|
104
|
-
tail = $2
|
105
|
-
else
|
106
|
-
base = 'For each'
|
107
|
-
tail = lhs
|
108
|
-
end
|
109
|
-
|
110
|
-
raise 'The \'exists\' quantifier is not yet supported.' if base =~ /exists/
|
111
|
-
|
112
|
-
if tail =~ @@mode_re
|
113
|
-
mode = :inherits
|
114
|
-
tail = $3
|
115
|
-
else
|
116
|
-
mode = :equals
|
117
|
-
end
|
118
|
-
|
119
|
-
# check if there is a where clause
|
120
|
-
if tail =~ @@where_re
|
121
|
-
head = $1.strip
|
122
|
-
tail = $2
|
123
|
-
else
|
124
|
-
head = tail.strip
|
125
|
-
tail = nil
|
126
|
-
end
|
127
|
-
|
128
|
-
# match the class type and tag
|
129
|
-
if head != ''
|
130
|
-
head =~ @@head_re
|
131
|
-
clazz = @rulebook.__eval__ $1
|
132
|
-
tag = $3 ? $3.to_sym : GeneratedTag.new
|
133
|
-
else
|
134
|
-
clazz = Object
|
135
|
-
tag = GeneratedTag.new
|
136
|
-
mode = :inherits
|
137
|
-
end
|
138
|
-
|
139
|
-
deftemplate = Core::Template.new clazz, mode
|
140
|
-
head = Core::HeadAtom.new tag, deftemplate
|
141
|
-
|
142
|
-
atoms = []
|
143
|
-
atom_strs = tail ? tail.split(@@and_re) : []
|
144
|
-
atom_strs.each do |a|
|
145
|
-
# BUG we also need to pass in the head_tag with atoms!
|
146
|
-
atoms.push parse_atom(a, deftemplate, atoms)
|
147
|
-
end
|
148
|
-
|
149
|
-
if base =~ /not\??/
|
150
|
-
p = mode==:inherits ? Core::NotInheritsPattern.new(head, atoms) :
|
151
|
-
Core::NotPattern.new(head, atoms)
|
152
|
-
else
|
153
|
-
p = mode==:inherits ? Core::InheritsPattern.new(head, atoms) :
|
154
|
-
Core::ObjectPattern.new(head, atoms)
|
155
|
-
end
|
156
|
-
|
157
|
-
pattern = pattern ? Core::AndPattern.new(pattern, p) : p
|
158
|
-
end
|
159
|
-
return pattern
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
def parse_atom(str, deftemplate, atoms)
|
164
|
-
expression, tag = nil, nil
|
165
|
-
if str =~ @@tag_re
|
166
|
-
expression, tag = $1, $2.strip.to_sym
|
167
|
-
else
|
168
|
-
expression, tag = str, GeneratedTag.new
|
169
|
-
end
|
170
|
-
|
171
|
-
bindings = []
|
172
|
-
uniq_binds = []
|
173
|
-
expression.scan(@@bind_re).each do |b|
|
174
|
-
# HACK how can we create a truely unique variable name?
|
175
|
-
uniq_bind = "ruleby_unique_variable_name_#{b[0]}"
|
176
|
-
uniq_binds.push uniq_bind
|
177
|
-
expression.sub!(/#:#{b[0]}/, uniq_bind)
|
178
|
-
bindings.push b[0].strip.to_sym
|
179
|
-
end
|
180
|
-
|
181
|
-
raise @@method_error + expression unless expression =~ @@method_re
|
182
|
-
method = $1
|
183
|
-
expression.gsub!(/##{method}/, method)
|
184
|
-
expression = "true" if expression.strip == method
|
185
|
-
|
186
|
-
proc = "lambda {|#{method}"
|
187
|
-
|
188
|
-
uniq_binds.each do |b|
|
189
|
-
# TODO make sure 'b' is not equal to 'method' or other b's
|
190
|
-
proc += ",#{b}"
|
191
|
-
end
|
192
|
-
|
193
|
-
proc += "| #{expression} }"
|
194
|
-
|
195
|
-
block = eval proc
|
196
|
-
|
197
|
-
if bindings.empty?
|
198
|
-
return Core::PropertyAtom.new(tag, method, deftemplate, &block)
|
199
|
-
elsif references_self?(bindings, atoms)
|
200
|
-
bound_methods = resolve_bindings(bindings, atoms)
|
201
|
-
return Core::SelfReferenceAtom.new(tag, method, bound_methods, deftemplate, &block)
|
202
|
-
else
|
203
|
-
return Core::ReferenceAtom.new(tag, method, bindings, deftemplate, &block)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def references_self?(bindings, atoms)
|
208
|
-
ref_self = 0
|
209
|
-
bindings.each do |b|
|
210
|
-
atoms.each do |a|
|
211
|
-
if (a.tag == b)
|
212
|
-
ref_self += 1
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
if ref_self > 0 and ref_self != bindings.size
|
218
|
-
raise 'Binding to self and another pattern in the same expression is not yet supported.'
|
219
|
-
end
|
220
|
-
|
221
|
-
return ref_self > 0
|
222
|
-
end
|
223
|
-
|
224
|
-
def resolve_bindings(bindings, atoms)
|
225
|
-
bound_methods = []
|
226
|
-
bindings.each do |b|
|
227
|
-
atoms.each do |a|
|
228
|
-
if a.tag == b
|
229
|
-
bound_methods.push a.method
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
return bound_methods
|
234
|
-
end
|
235
|
-
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
data/lib/dsl/steel.rb
DELETED
@@ -1,314 +0,0 @@
|
|
1
|
-
# This file is part of the Ruleby project (http://ruleby.org)
|
2
|
-
#
|
3
|
-
# This application is free software; you can redistribute it and/or
|
4
|
-
# modify it under the terms of the Ruby license defined in the
|
5
|
-
# LICENSE.txt file.
|
6
|
-
#
|
7
|
-
# Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
|
8
|
-
#
|
9
|
-
# * Authors: Matt Smith
|
10
|
-
#
|
11
|
-
|
12
|
-
module Ruleby
|
13
|
-
module Steel
|
14
|
-
class RulebookHelper
|
15
|
-
|
16
|
-
include Ruleby
|
17
|
-
def initialize(engine)
|
18
|
-
raise 'This DSL is deprecated'
|
19
|
-
@engine = engine
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :engine
|
23
|
-
|
24
|
-
def rule(name, &block)
|
25
|
-
r = Steel::RuleBuilder.new name
|
26
|
-
yield r if block_given?
|
27
|
-
@engine.assert_rule r.build_rule
|
28
|
-
r
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
class RuleBuilder
|
34
|
-
|
35
|
-
def initialize(name, pattern=nil, action=nil, priority=0)
|
36
|
-
@name = name
|
37
|
-
@pattern = pattern
|
38
|
-
@action = action
|
39
|
-
@priority = priority
|
40
|
-
end
|
41
|
-
|
42
|
-
def when(&block)
|
43
|
-
wb = WhenBuilder.new
|
44
|
-
yield wb
|
45
|
-
@pattern = wb.pattern
|
46
|
-
end
|
47
|
-
|
48
|
-
def then(&block)
|
49
|
-
@action = Core::Action.new(&block)
|
50
|
-
@action.name = @name
|
51
|
-
@action.priority = @priority
|
52
|
-
end
|
53
|
-
|
54
|
-
def when=(pattern)
|
55
|
-
@pattern = pattern
|
56
|
-
end
|
57
|
-
|
58
|
-
def then=(action)
|
59
|
-
@action = action
|
60
|
-
@action.name = @name
|
61
|
-
@action.priority = @priority
|
62
|
-
end
|
63
|
-
|
64
|
-
def priority
|
65
|
-
return @priority
|
66
|
-
end
|
67
|
-
|
68
|
-
def priority=(p)
|
69
|
-
@priority = p
|
70
|
-
@action.priority = @priority
|
71
|
-
end
|
72
|
-
|
73
|
-
def build_rule
|
74
|
-
r = Ruleby::Core::Rule.new @name, @pattern, @action, @priority
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class WhenBuilder #< RulebookHelper
|
79
|
-
def initialize()
|
80
|
-
@pattern_hash = Hash.new
|
81
|
-
@pattern_keys = []
|
82
|
-
end
|
83
|
-
|
84
|
-
def method_missing(method_id, *args, &block)
|
85
|
-
method = method_id.to_sym
|
86
|
-
wi = nil
|
87
|
-
if @pattern_hash.key? method
|
88
|
-
wi = @pattern_hash[method]
|
89
|
-
elsif :not == method
|
90
|
-
@pattern_keys.push method
|
91
|
-
return self
|
92
|
-
else
|
93
|
-
wi = WhenInternal.new method, args[0]
|
94
|
-
@pattern_hash[method] = wi
|
95
|
-
@pattern_keys.push method
|
96
|
-
end
|
97
|
-
return wi
|
98
|
-
end
|
99
|
-
|
100
|
-
def pattern
|
101
|
-
operands = []
|
102
|
-
nt = false
|
103
|
-
@pattern_keys.each do |key|
|
104
|
-
if :not != key
|
105
|
-
wi = @pattern_hash[key]
|
106
|
-
tag = wi.tag
|
107
|
-
type = wi.type
|
108
|
-
atoms = wi.to_atoms
|
109
|
-
p = nil
|
110
|
-
if nt
|
111
|
-
p = Ruleby::Core::NotPattern.new(tag, type, atoms)
|
112
|
-
nt = false
|
113
|
-
else
|
114
|
-
p = Ruleby::Core::ObjectPattern.new(tag, type, atoms)
|
115
|
-
end
|
116
|
-
operands = operands + [p]
|
117
|
-
else
|
118
|
-
nt = true
|
119
|
-
end
|
120
|
-
end
|
121
|
-
return and_pattern(operands)
|
122
|
-
end
|
123
|
-
|
124
|
-
def and_pattern(operands)
|
125
|
-
# TODO raise exception if referenceAtoms from the right do not
|
126
|
-
# have the values they referenece in the left
|
127
|
-
# TODO raise exception if there are repeated tags?
|
128
|
-
left = nil
|
129
|
-
operands.each do |operand|
|
130
|
-
if left.nil?
|
131
|
-
left = operand
|
132
|
-
else
|
133
|
-
right = operand
|
134
|
-
left = Ruleby::Core::AndPattern.new(left, right)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
left
|
138
|
-
end
|
139
|
-
|
140
|
-
def or_pattern(operands)
|
141
|
-
# TODO raise exception if referenceAtoms from the right do not
|
142
|
-
# have the values they referenece in the left
|
143
|
-
# TODO raise exception if there are repeated tags?
|
144
|
-
left = nil
|
145
|
-
operands.each do |operand|
|
146
|
-
if left.nil?
|
147
|
-
left = operand
|
148
|
-
else
|
149
|
-
right = operand
|
150
|
-
left = Ruleby::Core::OrPattern.new(left, right)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
left
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
class WhenInternal
|
158
|
-
public_instance_methods.each do |m|
|
159
|
-
a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
|
160
|
-
undef_method m.to_sym unless a.include? m.to_sym
|
161
|
-
end
|
162
|
-
|
163
|
-
attr_reader :tag, :type
|
164
|
-
def initialize(tag, type)
|
165
|
-
@tag = tag
|
166
|
-
@type = type
|
167
|
-
@builder = WhenPropertyBuilder.new self
|
168
|
-
end
|
169
|
-
|
170
|
-
def to_atoms
|
171
|
-
atoms = []
|
172
|
-
tags = {@tag => :class}
|
173
|
-
@builder.property_hash.each_value do |wp|
|
174
|
-
tags[wp.tag] = wp.name if wp.tag
|
175
|
-
end
|
176
|
-
@builder.property_keys.each do |key|
|
177
|
-
wp = @builder.property_hash[key]
|
178
|
-
atoms = atoms + [wp.to_atom(tags)]
|
179
|
-
end
|
180
|
-
return atoms
|
181
|
-
end
|
182
|
-
|
183
|
-
def &
|
184
|
-
return self
|
185
|
-
end
|
186
|
-
|
187
|
-
def method_missing(method_id, *args, &block)
|
188
|
-
m = method_id.to_s
|
189
|
-
suffix = m.to_s[-1..-1]
|
190
|
-
if suffix == '='
|
191
|
-
new_m = m[0,m.size-1]
|
192
|
-
if args[0].class == Array && args[0].size > 1 && args[0][1] == :%
|
193
|
-
wp = @builder.create new_m do |x,y| x == y end
|
194
|
-
wp.references args[0][0]
|
195
|
-
return wp
|
196
|
-
else
|
197
|
-
wp = @builder.create new_m do |x| x == args[0] end
|
198
|
-
return wp
|
199
|
-
end
|
200
|
-
else
|
201
|
-
wp = @builder.create(m, &block)
|
202
|
-
if args.size > 0 && args[0]
|
203
|
-
if block_given?
|
204
|
-
wp.references args[0]
|
205
|
-
else
|
206
|
-
wp.tag = args[0]
|
207
|
-
end
|
208
|
-
end
|
209
|
-
return wp
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
class WhenPropertyBuilder
|
215
|
-
attr_reader:property_hash
|
216
|
-
attr_reader:property_keys
|
217
|
-
|
218
|
-
def initialize(parent)
|
219
|
-
@parent = parent
|
220
|
-
@property_hash = Hash.new
|
221
|
-
@property_keys = []
|
222
|
-
end
|
223
|
-
|
224
|
-
def create(method_id,&block)
|
225
|
-
method = method_id.to_sym
|
226
|
-
wp = nil
|
227
|
-
if @property_hash.key? method
|
228
|
-
wp = @property_hash[method]
|
229
|
-
else
|
230
|
-
wp = WhenProperty.new @parent, method do |p| true end
|
231
|
-
@property_hash[method] = wp
|
232
|
-
@property_keys.push method
|
233
|
-
end
|
234
|
-
if block_given?
|
235
|
-
wp.block = block
|
236
|
-
end
|
237
|
-
return wp
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
class WhenProperty
|
242
|
-
|
243
|
-
def initialize(parent,name, &block)
|
244
|
-
@tag = nil
|
245
|
-
@name = name
|
246
|
-
@references = nil
|
247
|
-
@block = block
|
248
|
-
@parent = parent
|
249
|
-
end
|
250
|
-
attr:tag,true
|
251
|
-
attr:type,true
|
252
|
-
attr:value,true
|
253
|
-
attr_reader:name
|
254
|
-
attr_accessor:block
|
255
|
-
|
256
|
-
def &
|
257
|
-
return @parent
|
258
|
-
end
|
259
|
-
|
260
|
-
def bind(n)
|
261
|
-
@tag = n
|
262
|
-
end
|
263
|
-
|
264
|
-
def not=(value,ref=nil)
|
265
|
-
if ref && ref == :%
|
266
|
-
raise 'Using \'not=\' for references is not yet supported'
|
267
|
-
set_block do |x,y| x != y end
|
268
|
-
references value
|
269
|
-
else
|
270
|
-
set_block do |s| s != value end
|
271
|
-
end
|
272
|
-
|
273
|
-
end
|
274
|
-
def set_block(&block)
|
275
|
-
@block = block
|
276
|
-
end
|
277
|
-
private:set_block
|
278
|
-
|
279
|
-
def references(refs)
|
280
|
-
@references = refs
|
281
|
-
end
|
282
|
-
|
283
|
-
def to_atom(pattern_tags)
|
284
|
-
unless @tag
|
285
|
-
@tag = GeneratedTag.new
|
286
|
-
end
|
287
|
-
if @references
|
288
|
-
@references = [@references] unless @references.kind_of?(Array)
|
289
|
-
i = includes_how_many(@references, pattern_tags.keys)
|
290
|
-
if i == 0
|
291
|
-
return Ruleby::Core::ReferenceAtom.new(@tag, @name, @references, @parent.type, &@block)
|
292
|
-
elsif i == @references.size
|
293
|
-
refs = @references.collect{|r| pattern_tags[r] }
|
294
|
-
return Ruleby::Core::SelfReferenceAtom.new(@tag, @name, refs, @parent.type, &@block)
|
295
|
-
else
|
296
|
-
raise 'Referencing self AND other patterns in the same atom is not yet supported'
|
297
|
-
end
|
298
|
-
else
|
299
|
-
return Ruleby::Core::PropertyAtom.new(@tag, @name, @parent.type, &@block)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
private
|
304
|
-
def includes_how_many(list1, list2)
|
305
|
-
i = 0
|
306
|
-
list2.each do |a|
|
307
|
-
i += 1 if list1.include?(a)
|
308
|
-
end
|
309
|
-
return i
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
end
|
314
|
-
end
|