ruleby 0.2 → 0.3
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/basic_rules.rb +66 -0
- data/benchmarks/joined_rules.rb +73 -0
- data/benchmarks/miss_manners/data.rb +11 -0
- data/benchmarks/miss_manners/miss_manners.rb +11 -1
- data/benchmarks/miss_manners/model.rb +11 -0
- data/benchmarks/miss_manners/rules.rb +49 -110
- data/benchmarks/model.rb +36 -0
- data/examples/example_diagnosis.rb +35 -73
- data/examples/example_hello.rb +20 -22
- data/examples/example_politician.rb +22 -11
- data/examples/example_ticket.rb +40 -85
- data/examples/fibonacci_example1.rb +13 -2
- data/examples/fibonacci_example2.rb +11 -0
- data/examples/fibonacci_rulebook.rb +58 -111
- data/examples/test_self_reference.rb +51 -9
- data/lib/core/atoms.rb +53 -116
- data/lib/core/engine.rb +96 -96
- data/lib/core/nodes.rb +330 -298
- data/lib/core/patterns.rb +36 -39
- data/lib/core/utils.rb +141 -3
- data/lib/dsl/ferrari.rb +263 -0
- data/lib/dsl/letigre.rb +212 -0
- data/lib/dsl/steel.rb +313 -0
- data/lib/rulebook.rb +82 -265
- data/lib/ruleby.rb +13 -0
- metadata +21 -19
- data/benchmarks/50_joined_rules.rb +0 -78
- data/benchmarks/50_rules.rb +0 -57
- data/benchmarks/5_joined_rules.rb +0 -78
- data/benchmarks/5_rules.rb +0 -57
data/lib/rulebook.rb
CHANGED
@@ -1,285 +1,102 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def rule(name, &block)
|
12
|
-
r = Core::Rule.new name
|
13
|
-
yield r if block_given?
|
14
|
-
@engine.assert_rule r
|
15
|
-
r
|
16
|
-
end
|
17
|
-
|
18
|
-
def action(&block)
|
19
|
-
return Core::Action.new(&block)
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
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, Joe Kutner
|
10
|
+
#
|
23
11
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
42
|
-
end
|
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]
|
62
|
-
else
|
63
|
-
nt = true
|
64
|
-
end
|
65
|
-
end
|
66
|
-
return and_pattern(operands)
|
67
|
-
end
|
68
|
-
end
|
12
|
+
require 'ruleby'
|
13
|
+
require 'dsl/ferrari'
|
14
|
+
require 'dsl/letigre'
|
15
|
+
require 'dsl/steel'
|
69
16
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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)]
|
17
|
+
module Ruleby
|
18
|
+
class Rulebook
|
19
|
+
include Ruleby
|
20
|
+
def initialize(engine)
|
21
|
+
@engine = engine
|
92
22
|
end
|
93
|
-
return atoms
|
94
|
-
end
|
95
23
|
|
96
|
-
|
97
|
-
return self
|
98
|
-
end
|
24
|
+
attr_reader :engine
|
99
25
|
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
109
|
-
else
|
110
|
-
wp = @builder.create new_m do |x| x == args[0] end
|
111
|
-
return wp
|
26
|
+
def rule(*args, &block)
|
27
|
+
unless args.empty?
|
28
|
+
name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
|
112
29
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
30
|
+
|
31
|
+
if args.empty?
|
32
|
+
# use steel DSL
|
33
|
+
r = Steel::RulebookHelper.new @engine
|
34
|
+
r.rule name, &block
|
35
|
+
else
|
36
|
+
i = args[0].kind_of?(Hash) ? 1 : 0
|
37
|
+
if args[i].kind_of? Array
|
38
|
+
# use ferrari DSL
|
39
|
+
r = Ferrari::RulebookHelper.new @engine
|
40
|
+
r.rule name, *args, &block
|
41
|
+
elsif args[i].kind_of? String
|
42
|
+
# use letigre DSL
|
43
|
+
r = LeTigre::RulebookHelper.new @engine
|
44
|
+
r.rule name, *args, &block
|
118
45
|
else
|
119
|
-
|
46
|
+
raise 'Rule format not recognized.'
|
120
47
|
end
|
121
48
|
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
|
146
49
|
end
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
return wp
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
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
|
50
|
+
|
51
|
+
def m
|
52
|
+
Ruleby::Ferrari::MethodBuilder.new
|
182
53
|
end
|
183
54
|
|
184
|
-
|
185
|
-
|
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
|
55
|
+
def method
|
56
|
+
m
|
197
57
|
end
|
198
|
-
|
199
|
-
|
200
|
-
|
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)
|
205
|
-
else
|
206
|
-
raise 'Referencing self AND other patterns in the same atom is not yet supported'
|
207
|
-
end
|
208
|
-
else
|
209
|
-
return Core::PropertyAtom.new(@tag, @name, &@block)
|
58
|
+
|
59
|
+
def b(variable_name)
|
60
|
+
Ruleby::Ferrari::BindingBuilder.new(variable_name)
|
210
61
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
def includes_how_many(list1, list2)
|
215
|
-
i = 0
|
216
|
-
list2.each do |a|
|
217
|
-
i += 1 if list1.include?(a)
|
62
|
+
|
63
|
+
def binding(variable_name)
|
64
|
+
b
|
218
65
|
end
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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)
|
66
|
+
|
67
|
+
def c(&block)
|
68
|
+
return lambda(&block)
|
238
69
|
end
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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)
|
70
|
+
|
71
|
+
def condition(&block)
|
72
|
+
return lambda(&block)
|
254
73
|
end
|
255
|
-
end
|
256
|
-
left
|
257
|
-
end
|
258
|
-
|
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
|
272
|
-
end
|
74
|
+
end
|
273
75
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
76
|
+
class GeneratedTag
|
77
|
+
# this counter is incremented for each UniqueTag created, and is
|
78
|
+
# appended to the end of the unique_seed in order to create a
|
79
|
+
# string that is unique for each instance of this class.
|
80
|
+
@@tag_counter = 0
|
81
|
+
|
82
|
+
# every generated tag will be prefixed with this string
|
83
|
+
@@unique_seed = 'unique_seed'
|
281
84
|
|
282
|
-
|
283
|
-
|
85
|
+
def initialize()
|
86
|
+
@@tag_counter += 1
|
87
|
+
@tag = @@unique_seed + @@tag_counter.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader:tag_counter
|
91
|
+
attr_reader:unique_seed
|
92
|
+
attr_reader:tag
|
93
|
+
|
94
|
+
def ==(ut)
|
95
|
+
return ut && ut.kind_of?(GeneratedTag) && @tag == ut.tag
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
return @tag.to_s
|
100
|
+
end
|
284
101
|
end
|
285
|
-
end
|
102
|
+
end
|
data/lib/ruleby.rb
CHANGED
@@ -1,4 +1,17 @@
|
|
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, Matt Smith
|
10
|
+
#
|
11
|
+
|
1
12
|
require 'core/engine'
|
13
|
+
require 'rulebook'
|
14
|
+
|
2
15
|
module Ruleby
|
3
16
|
#helper classes for using ruleby go here
|
4
17
|
def engine(name, &block)
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: ruleby
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "0.
|
7
|
-
date: 2007-
|
6
|
+
version: "0.3"
|
7
|
+
date: 2007-09-03 00:00:00 -05:00
|
8
8
|
summary: the Rule Engine for Ruby
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,29 +29,31 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- Joe Kutner, Matt Smith
|
31
31
|
files:
|
32
|
-
- ./
|
33
|
-
- ./
|
34
|
-
- ./
|
35
|
-
- ./
|
36
|
-
- ./
|
32
|
+
- ./lib/ruleby.rb
|
33
|
+
- ./lib/rulebook.rb
|
34
|
+
- ./lib/dsl/ferrari.rb
|
35
|
+
- ./lib/dsl/letigre.rb
|
36
|
+
- ./lib/dsl/steel.rb
|
37
|
+
- ./lib/core/patterns.rb
|
38
|
+
- ./lib/core/utils.rb
|
39
|
+
- ./lib/core/atoms.rb
|
40
|
+
- ./lib/core/engine.rb
|
41
|
+
- ./lib/core/nodes.rb
|
42
|
+
- ./benchmarks/model.rb
|
43
|
+
- ./benchmarks/joined_rules.rb
|
44
|
+
- ./benchmarks/basic_rules.rb
|
37
45
|
- ./benchmarks/miss_manners/miss_manners.rb
|
38
46
|
- ./benchmarks/miss_manners/model.rb
|
47
|
+
- ./benchmarks/miss_manners/data.rb
|
39
48
|
- ./benchmarks/miss_manners/rules.rb
|
40
|
-
- ./examples/example_diagnosis.rb
|
41
|
-
- ./examples/example_hello.rb
|
42
|
-
- ./examples/example_politician.rb
|
43
|
-
- ./examples/example_ticket.rb
|
44
49
|
- ./examples/fibonacci_example1.rb
|
45
50
|
- ./examples/fibonacci_example2.rb
|
51
|
+
- ./examples/example_hello.rb
|
52
|
+
- ./examples/example_ticket.rb
|
46
53
|
- ./examples/fibonacci_rulebook.rb
|
54
|
+
- ./examples/example_politician.rb
|
47
55
|
- ./examples/test_self_reference.rb
|
48
|
-
- ./
|
49
|
-
- ./lib/ruleby.rb
|
50
|
-
- ./lib/core/atoms.rb
|
51
|
-
- ./lib/core/engine.rb
|
52
|
-
- ./lib/core/nodes.rb
|
53
|
-
- ./lib/core/patterns.rb
|
54
|
-
- ./lib/core/utils.rb
|
56
|
+
- ./examples/example_diagnosis.rb
|
55
57
|
test_files: []
|
56
58
|
|
57
59
|
rdoc_options: []
|
@@ -1,78 +0,0 @@
|
|
1
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
|
2
|
-
require 'ruleby'
|
3
|
-
require 'rulebook'
|
4
|
-
|
5
|
-
class Account
|
6
|
-
def initialize(status, title, account_id)
|
7
|
-
@status = status
|
8
|
-
@title = title
|
9
|
-
@account_id = account_id
|
10
|
-
end
|
11
|
-
|
12
|
-
attr :status, true
|
13
|
-
attr :title, true
|
14
|
-
attr :account_id, true
|
15
|
-
end
|
16
|
-
|
17
|
-
class Address
|
18
|
-
def initialize(addr_id, city, state, zip)
|
19
|
-
@addr_id = addr_id
|
20
|
-
@city = city
|
21
|
-
@state = state
|
22
|
-
@zip = zip
|
23
|
-
end
|
24
|
-
|
25
|
-
attr :addr_id, true
|
26
|
-
attr :city, true
|
27
|
-
attr :state, true
|
28
|
-
attr :zip, true
|
29
|
-
end
|
30
|
-
|
31
|
-
class TestRulebook < Rulebook
|
32
|
-
def rules
|
33
|
-
(0..50).each do |index|
|
34
|
-
rule "Rule-#{index}" do |r|
|
35
|
-
r.when do |has|
|
36
|
-
has.a Account
|
37
|
-
has.a.status = 'standard'
|
38
|
-
|
39
|
-
has.addr Address
|
40
|
-
has.addr.addr_id = "acc#{index}"
|
41
|
-
has.addr.city = 'Foobar'
|
42
|
-
has.addr.state = 'FB'
|
43
|
-
has.addr.zip = '12345'
|
44
|
-
end
|
45
|
-
|
46
|
-
r.then do |e,vars|
|
47
|
-
puts "rule #{index} fired"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
include Ruleby
|
55
|
-
|
56
|
-
t1 = Time.new
|
57
|
-
engine :engine do |e|
|
58
|
-
TestRulebook.new(e).rules
|
59
|
-
|
60
|
-
t2 = Time.new
|
61
|
-
diff = t2.to_f - t1.to_f
|
62
|
-
puts 'time to create rule set: ' + diff.to_s
|
63
|
-
|
64
|
-
e.assert Account.new('standard', nil, nil)
|
65
|
-
for k in (0..500)
|
66
|
-
e.assert Address.new(('acc'+k.to_s),'Foobar', 'FB', '12345')
|
67
|
-
end
|
68
|
-
|
69
|
-
t3 = Time.new
|
70
|
-
diff = t3.to_f - t2.to_f
|
71
|
-
puts 'time to assert facts: ' + diff.to_s
|
72
|
-
|
73
|
-
e.match
|
74
|
-
|
75
|
-
t4 = Time.new
|
76
|
-
diff = t4.to_f - t3.to_f
|
77
|
-
puts 'time to match rules: ' + diff.to_s
|
78
|
-
end
|