ruleby 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,16 +2,16 @@ require 'ruleby'
2
2
 
3
3
  class Rulebook
4
4
  include Ruleby
5
- def initialize(rete)
6
- @rete = rete
5
+ def initialize(engine)
6
+ @engine = engine
7
7
  end
8
8
 
9
- attr_reader :rete
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
- @rete.assert_rule r
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
- class PatternInternal
31
- def initialize(tag, type)
32
- @tag = tag
33
- @type = type
34
- @atoms = []
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
- def atom(tag, name, references, &block)
38
- if references
39
- references = [references] unless references.kind_of?(Array)
40
- return Core::ReferenceAtom.new(tag, name, references, &block)
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
- return Core::PropertyAtom.new(tag, name, &block)
63
+ nt = true
43
64
  end
44
65
  end
45
- def method_missing(method_id, *args, &block)
46
- if block_given?
47
- with(method_id.to_s, *args, &block)
48
- else
49
- with(method_id.to_s, *args)
50
- end
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
- def with(property, options={}, &block)
53
- tag = options[:name]
54
- tag = GeneratedTag.new if tag == options.default
55
- if block_given?
56
- @atoms.push atom(tag, property, options[:references], &block)
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
- # QUESTION do we really want to do this? What if someone does:
59
- # :equals => -1
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
- def pattern
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
- end
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
- class PatternBuilder < Rulebook
90
-
91
- attr_reader :condition
92
-
93
- def initialize()
94
- @condition = nil
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
- def has(type, tag=GeneratedTag.new, &block)
98
- pi = pattern_internal(tag, type)
99
- yield pi if block_given?
100
- op = pi.pattern
101
- if(@condition)
102
- pat = [@condition, op]
103
- @condition = and_pattern(pat)
104
- else
105
- @condition = op
106
- end
107
- @condition
108
- end
109
- def and(operands)
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
- def or(operands)
120
- if(@condition)
121
- p = [@condition] + operands
122
- @condition = or_pattern(p)
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
- @condition = or_pattern(operands)
206
+ raise 'Referencing self AND other patterns in the same atom is not yet supported'
125
207
  end
126
- @condition
208
+ else
209
+ return Core::PropertyAtom.new(@tag, @name, &@block)
127
210
  end
128
-
129
- def not(type, tag=GeneratedTag.new, &block)
130
- pi = pattern_internal(tag, type)
131
- yield pi if block_given?
132
- n = pi.not
133
- if(@condition)
134
- p = [@condition, n]
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
- private
142
- def pattern_internal(tag, type)
143
- return PatternInternal.new(tag, type)
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
- def or_pattern(operands)
147
- # TODO raise exception if referenceAtoms from the right do not
148
- # have the values they referenece in the left
149
- # TODO raise exception if there are repeated tags?
150
- left = nil
151
- operands.each do |operand|
152
- if left.nil?
153
- left = operand
154
- else
155
- right = operand
156
- left = Core::OrPattern.new(left, right)
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
- def and_pattern(operands)
163
- # TODO raise exception if referenceAtoms from the right do not
164
- # have the values they referenece in the left
165
- # TODO raise exception if there are repeated tags?
166
- left = nil
167
- operands.each do |operand|
168
- if left.nil?
169
- left = operand
170
- else
171
- right = operand
172
- left = Core::AndPattern.new(left, right)
173
- end
174
- end
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
- class GeneratedTag
274
+ attr_reader:tag_counter
275
+ attr_reader:unique_seed
276
+ attr_reader:tag
180
277
 
181
- # this counter is incremented for each UniqueTag created, and is
182
- # appended to the end of the unique_seed in order to create a
183
- # string that is unique for each instance of this class.
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
- def initialize()
190
- @@tag_counter += 1
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
@@ -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