ruleby 0.1 → 0.2
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/benchmarks/50_joined_rules.rb +78 -0
- data/benchmarks/50_rules.rb +57 -0
- data/benchmarks/5_joined_rules.rb +78 -0
- data/benchmarks/5_rules.rb +57 -0
- data/benchmarks/miss_manners/data.rb +135 -0
- data/benchmarks/miss_manners/miss_manners.rb +23 -0
- data/benchmarks/miss_manners/model.rb +182 -0
- data/benchmarks/miss_manners/rules.rb +165 -0
- data/examples/example_diagnosis.rb +155 -0
- data/examples/example_hello.rb +48 -0
- data/examples/example_politician.rb +86 -0
- data/examples/example_ticket.rb +158 -0
- data/examples/fibonacci_example1.rb +33 -0
- data/examples/fibonacci_example2.rb +29 -0
- data/examples/fibonacci_rulebook.rb +137 -0
- data/examples/test_self_reference.rb +35 -0
- data/lib/core/atoms.rb +13 -71
- data/lib/core/engine.rb +47 -9
- data/lib/core/nodes.rb +487 -548
- data/lib/core/patterns.rb +0 -110
- data/lib/core/utils.rb +120 -184
- data/lib/rulebook.rb +237 -157
- data/lib/ruleby.rb +0 -20
- metadata +28 -11
data/lib/rulebook.rb
CHANGED
@@ -2,16 +2,16 @@ require 'ruleby'
|
|
2
2
|
|
3
3
|
class Rulebook
|
4
4
|
include Ruleby
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(engine)
|
6
|
+
@engine = engine
|
7
7
|
end
|
8
8
|
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :engine
|
10
10
|
|
11
11
|
def rule(name, &block)
|
12
12
|
r = Core::Rule.new name
|
13
13
|
yield r if block_given?
|
14
|
-
@
|
14
|
+
@engine.assert_rule r
|
15
15
|
r
|
16
16
|
end
|
17
17
|
|
@@ -19,187 +19,267 @@ class Rulebook
|
|
19
19
|
return Core::Action.new(&block)
|
20
20
|
end
|
21
21
|
|
22
|
-
def pattern(&block)
|
23
|
-
pb = PatternBuilder.new
|
24
|
-
yield pb
|
25
|
-
return pb.condition
|
26
|
-
end
|
27
22
|
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
class WhenBuilder < Rulebook
|
25
|
+
def initialize()
|
26
|
+
@pattern_hash = Hash.new
|
27
|
+
@pattern_keys = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method_id, *args, &block)
|
31
|
+
method = method_id.to_sym
|
32
|
+
wi = nil
|
33
|
+
if @pattern_hash.key? method
|
34
|
+
wi = @pattern_hash[method]
|
35
|
+
elsif :not == method
|
36
|
+
@pattern_keys.push method
|
37
|
+
return self
|
38
|
+
else
|
39
|
+
wi = WhenInternal.new method, args[0]
|
40
|
+
@pattern_hash[method] = wi
|
41
|
+
@pattern_keys.push method
|
35
42
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
return wi
|
44
|
+
end
|
45
|
+
def pattern
|
46
|
+
operands = []
|
47
|
+
nt = false
|
48
|
+
@pattern_keys.each do |key|
|
49
|
+
if :not != key
|
50
|
+
wi = @pattern_hash[key]
|
51
|
+
tag = wi.tag
|
52
|
+
type = wi.type
|
53
|
+
atoms = wi.to_atoms
|
54
|
+
p = nil
|
55
|
+
if nt
|
56
|
+
p = Core::NotPattern.new(tag, type, atoms)
|
57
|
+
nt = false
|
58
|
+
else
|
59
|
+
p = Core::ObjectPattern.new(tag, type, atoms)
|
60
|
+
end
|
61
|
+
operands = operands + [p]
|
41
62
|
else
|
42
|
-
|
63
|
+
nt = true
|
43
64
|
end
|
44
65
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
66
|
+
return and_pattern(operands)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class WhenInternal
|
71
|
+
public_instance_methods.each do |m|
|
72
|
+
a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
|
73
|
+
undef_method m.to_sym unless a.include? m.to_sym
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :tag, :type
|
77
|
+
def initialize(tag, type)
|
78
|
+
@tag = tag
|
79
|
+
@type = type
|
80
|
+
@builder = WhenPropertyBuilder.new self
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_atoms
|
84
|
+
atoms = []
|
85
|
+
tags = [@tag]
|
86
|
+
@builder.property_hash.each_value do |wp|
|
87
|
+
tags.push wp.tag if wp.tag
|
88
|
+
end
|
89
|
+
@builder.property_keys.each do |key|
|
90
|
+
wp = @builder.property_hash[key]
|
91
|
+
atoms = atoms + [wp.to_atom(tags)]
|
51
92
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
93
|
+
return atoms
|
94
|
+
end
|
95
|
+
|
96
|
+
def &
|
97
|
+
return self
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_missing(method_id, *args, &block)
|
101
|
+
m = method_id.to_s
|
102
|
+
suffix = m.to_s[-1..-1]
|
103
|
+
if suffix == '='
|
104
|
+
new_m = m[0,m.size-1]
|
105
|
+
if args[0].class == Array && args[0].size > 1 && args[0][1] == :%
|
106
|
+
wp = @builder.create new_m do |x,y| x == y end
|
107
|
+
wp.references args[0][0]
|
108
|
+
return wp
|
57
109
|
else
|
58
|
-
|
59
|
-
|
60
|
-
# How do we handle a case where it is not a ReferenceAtom? should we
|
61
|
-
# have a :equals_ref key too?
|
62
|
-
# if options[:equals] != options.default
|
63
|
-
# @atoms.push(atom(tag, property, [options[:equals]]) do |p,a| p==a end)
|
64
|
-
# elsif options[:not_equals] != options.default
|
65
|
-
# @atoms.push(atom(tag, property, [options[:not_equals]]) do |p,a| p!=a end)
|
66
|
-
# else
|
67
|
-
@atoms.push(atom(tag, property, nil) do |p| true end)
|
68
|
-
# end
|
110
|
+
wp = @builder.create new_m do |x| x == args[0] end
|
111
|
+
return wp
|
69
112
|
end
|
113
|
+
else
|
114
|
+
wp = @builder.create(m, &block)
|
115
|
+
if args.size > 0 && args[0]
|
116
|
+
if block_given?
|
117
|
+
wp.references args[0]
|
118
|
+
else
|
119
|
+
wp.tag = args[0]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
return wp
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class WhenPropertyBuilder
|
128
|
+
attr_reader:property_hash
|
129
|
+
attr_reader:property_keys
|
130
|
+
|
131
|
+
def initialize(parent)
|
132
|
+
@parent = parent
|
133
|
+
@property_hash = Hash.new
|
134
|
+
@property_keys = []
|
135
|
+
end
|
136
|
+
|
137
|
+
def create(method_id,&block)
|
138
|
+
method = method_id.to_sym
|
139
|
+
wp = nil
|
140
|
+
if @property_hash.key? method
|
141
|
+
wp = @property_hash[method]
|
142
|
+
else
|
143
|
+
wp = WhenProperty.new @parent, method do |p| true end
|
144
|
+
@property_hash[method] = wp
|
145
|
+
@property_keys.push method
|
70
146
|
end
|
71
|
-
|
72
|
-
|
73
|
-
@atoms = Array.new(1){@atoms} unless @atoms.kind_of?(Array)
|
74
|
-
return Core::ObjectPattern.new(@tag, @type, @atoms)
|
75
|
-
end
|
76
|
-
|
77
|
-
def not
|
78
|
-
@atoms = Array.new(1){@atoms} unless @atoms.kind_of?(Array)
|
79
|
-
return Core::NotPattern.new(@tag, @type, @atoms)
|
147
|
+
if block_given?
|
148
|
+
wp.block = block
|
80
149
|
end
|
81
|
-
|
82
|
-
class PatternInternal
|
83
|
-
public_instance_methods.each do |m|
|
84
|
-
a = [:not, :pattern, :with, :method_missing, :atom, :new, :public_instance_methods, :__send__, :__id__]
|
85
|
-
undef_method m.to_sym unless a.include? m.to_sym
|
150
|
+
return wp
|
86
151
|
end
|
87
152
|
end
|
88
153
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
154
|
+
class WhenProperty
|
155
|
+
attr_accessor :block
|
156
|
+
def initialize(parent,name, &block)
|
157
|
+
@tag = nil
|
158
|
+
@name = name
|
159
|
+
@references = nil
|
160
|
+
@block = block
|
161
|
+
@parent = parent
|
162
|
+
end
|
163
|
+
attr:tag,true
|
164
|
+
attr:type,true
|
165
|
+
attr:value,true
|
166
|
+
|
167
|
+
def &
|
168
|
+
return @parent
|
169
|
+
end
|
170
|
+
|
171
|
+
def bind(n)
|
172
|
+
@tag = n
|
173
|
+
end
|
174
|
+
|
175
|
+
def not=(value,ref=nil)
|
176
|
+
if ref && ref == :%
|
177
|
+
raise 'Using \'not=\' for references is not yet supported'
|
178
|
+
set_block do |x,y| x != y end
|
179
|
+
references value
|
180
|
+
else
|
181
|
+
set_block do |s| s != value end
|
95
182
|
end
|
96
183
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if @condition
|
111
|
-
p = [@condition] + operands
|
112
|
-
@condition = and_pattern(p)
|
113
|
-
else
|
114
|
-
@condition = and_pattern(operands)
|
115
|
-
end
|
116
|
-
@condition
|
184
|
+
end
|
185
|
+
def set_block(&block)
|
186
|
+
@block = block
|
187
|
+
end
|
188
|
+
private:set_block
|
189
|
+
|
190
|
+
def references(refs)
|
191
|
+
@references = refs
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_atom(pattern_tags)
|
195
|
+
unless @tag
|
196
|
+
@tag = GeneratedTag.new
|
117
197
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
@
|
198
|
+
if @references
|
199
|
+
@references = [@references] unless @references.kind_of?(Array)
|
200
|
+
i = includes_how_many(@references, pattern_tags)
|
201
|
+
if i == 0
|
202
|
+
return Core::ReferenceAtom.new(@tag, @name, @references, &@block)
|
203
|
+
elsif i == @references.size
|
204
|
+
return Core::SelfReferenceAtom.new(@tag, @name, @references, &@block)
|
123
205
|
else
|
124
|
-
|
206
|
+
raise 'Referencing self AND other patterns in the same atom is not yet supported'
|
125
207
|
end
|
126
|
-
|
208
|
+
else
|
209
|
+
return Core::PropertyAtom.new(@tag, @name, &@block)
|
127
210
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@condition = and_pattern(p)
|
136
|
-
else
|
137
|
-
@condition = n
|
138
|
-
end
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
def includes_how_many(list1, list2)
|
215
|
+
i = 0
|
216
|
+
list2.each do |a|
|
217
|
+
i += 1 if list1.include?(a)
|
139
218
|
end
|
219
|
+
return i
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
140
224
|
|
141
|
-
|
142
|
-
|
143
|
-
|
225
|
+
private
|
226
|
+
|
227
|
+
def or_pattern(operands)
|
228
|
+
# TODO raise exception if referenceAtoms from the right do not
|
229
|
+
# have the values they referenece in the left
|
230
|
+
# TODO raise exception if there are repeated tags?
|
231
|
+
left = nil
|
232
|
+
operands.each do |operand|
|
233
|
+
if left.nil?
|
234
|
+
left = operand
|
235
|
+
else
|
236
|
+
right = operand
|
237
|
+
left = Core::OrPattern.new(left, right)
|
144
238
|
end
|
239
|
+
end
|
240
|
+
left
|
241
|
+
end
|
145
242
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
159
|
-
left
|
243
|
+
def and_pattern(operands)
|
244
|
+
# TODO raise exception if referenceAtoms from the right do not
|
245
|
+
# have the values they referenece in the left
|
246
|
+
# TODO raise exception if there are repeated tags?
|
247
|
+
left = nil
|
248
|
+
operands.each do |operand|
|
249
|
+
if left.nil?
|
250
|
+
left = operand
|
251
|
+
else
|
252
|
+
right = operand
|
253
|
+
left = Core::AndPattern.new(left, right)
|
160
254
|
end
|
255
|
+
end
|
256
|
+
left
|
257
|
+
end
|
161
258
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
left
|
176
|
-
end
|
259
|
+
class GeneratedTag
|
260
|
+
|
261
|
+
# this counter is incremented for each UniqueTag created, and is
|
262
|
+
# appended to the end of the unique_seed in order to create a
|
263
|
+
# string that is unique for each instance of this class.
|
264
|
+
@@tag_counter = 0
|
265
|
+
|
266
|
+
# every generated tag will be prefixed with this string
|
267
|
+
@@unique_seed = 'unique_seed'
|
268
|
+
|
269
|
+
def initialize()
|
270
|
+
@@tag_counter += 1
|
271
|
+
@tag = @@unique_seed + @@tag_counter.to_s
|
177
272
|
end
|
178
273
|
|
179
|
-
|
274
|
+
attr_reader:tag_counter
|
275
|
+
attr_reader:unique_seed
|
276
|
+
attr_reader:tag
|
180
277
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
@@tag_counter = 0
|
185
|
-
|
186
|
-
# every generated tag will be prefixed with this string
|
187
|
-
@@unique_seed = 'unique_seed'
|
278
|
+
def ==(ut)
|
279
|
+
return ut && ut.kind_of?(GeneratedTag) && @tag == ut.tag
|
280
|
+
end
|
188
281
|
|
189
|
-
|
190
|
-
|
191
|
-
@tag = @@unique_seed + @@tag_counter.to_s
|
192
|
-
end
|
193
|
-
|
194
|
-
attr_reader:tag_counter
|
195
|
-
attr_reader:unique_seed
|
196
|
-
attr_reader:tag
|
197
|
-
|
198
|
-
def ==(ut)
|
199
|
-
return ut && ut.kind_of?(GeneratedTag) && @tag == ut.tag
|
200
|
-
end
|
201
|
-
|
202
|
-
def to_s
|
203
|
-
return @tag.to_s
|
204
|
-
end
|
282
|
+
def to_s
|
283
|
+
return @tag.to_s
|
205
284
|
end
|
285
|
+
end
|
data/lib/ruleby.rb
CHANGED
@@ -6,24 +6,4 @@ module Ruleby
|
|
6
6
|
yield e if block_given?
|
7
7
|
return e
|
8
8
|
end
|
9
|
-
def assert(rete,object,&block)
|
10
|
-
fact(rete,object,:plus,&block)
|
11
|
-
end
|
12
|
-
|
13
|
-
def retract(rete,object,&block)
|
14
|
-
fact(rete,object,:minus,&block)
|
15
|
-
end
|
16
|
-
|
17
|
-
def modify(rete,object,&block)
|
18
|
-
retract(rete,object,&block)
|
19
|
-
assert(rete,object,&block)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
def fact(rete, object, sign=:plus, &block)
|
24
|
-
f = Core::Fact.new object, sign
|
25
|
-
yield f if block_given?
|
26
|
-
rete.assert_fact f
|
27
|
-
f
|
28
|
-
end
|
29
9
|
end
|