ruleby 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,29 +19,27 @@ module Ruleby
19
19
  # object. It contains a list of 'atoms' that represent the properties of
20
20
  # the class that we are looking for.
21
21
  class ObjectPattern < Pattern
22
-
23
- attr_reader :head
24
22
  attr_reader :atoms
25
23
 
26
- # 'deftemplate' is a class type
27
- def initialize(tag, deftemplate, atoms)
28
- init_vars tag, deftemplate, atoms
24
+ def initialize(head, atoms)
25
+ @atoms = [head] + atoms
29
26
  end
30
27
 
31
- def init_vars(tag,deftemplate,atoms)
32
- @head = TypeAtom.new tag, deftemplate
33
- @atoms = [@head] + atoms
28
+ def head
29
+ @atoms[0]
34
30
  end
35
31
 
36
32
  def ==(pattern)
37
- atoms = pattern.atoms
38
- if(@atoms.size == atoms.size)
39
- (0..@atoms.size).each do |i|
40
- if !(@atoms[i] == atoms[i])
41
- return false
33
+ if pattern.class == self.class
34
+ atoms = pattern.atoms
35
+ if(@atoms.size == atoms.size)
36
+ (0..@atoms.size).each do |i|
37
+ if !(@atoms[i] == atoms[i])
38
+ return false
39
+ end
42
40
  end
41
+ return true
43
42
  end
44
- return true
45
43
  end
46
44
  return false
47
45
  end
@@ -51,13 +49,16 @@ module Ruleby
51
49
  end
52
50
  end
53
51
 
52
+ class InheritsPattern < ObjectPattern
53
+ end
54
+
54
55
  # This class represents a pattern that is looking for the absence of some
55
56
  # object (rather than the existence of). In all respects, it is the same as
56
57
  # an ObjectPattern, but it is handled differently by the inference engine.
57
58
  class NotPattern < ObjectPattern
58
- def ==(pattern)
59
- return pattern.kind_of?(NotPattern) && super==(pattern)
60
- end
59
+ end
60
+
61
+ class NotInheritsPattern < InheritsPattern
61
62
  end
62
63
 
63
64
  # A composite pattern represents a logical conjunction of two patterns. The
@@ -99,6 +100,8 @@ module Ruleby
99
100
 
100
101
  end
101
102
 
102
-
103
+ class PatternFactory
104
+
105
+ end
103
106
  end
104
107
  end
@@ -44,47 +44,52 @@ module Ruleby
44
44
  @name = name
45
45
  @pattern = pattern
46
46
  @action = action
47
- @priority = priority
47
+ @priority = priority
48
48
 
49
49
  @tags = {}
50
50
  @methods = {}
51
51
  @when_counter = 0
52
52
  end
53
53
 
54
- def when(*args)
55
- clazz = args.shift
54
+ def when(*args)
55
+ clazz = AtomBuilder === args[0] ? nil : args.shift
56
56
  is_not = false
57
- if clazz == :not || clazz == :~
58
- clazz = args.shift
59
- is_not = true
60
- elsif clazz == :exists
61
- raise 'The \'exists\' quantifier is not yet supported.'
57
+ mode = :equals
58
+ while clazz.is_a? Symbol
59
+ if clazz == :not || clazz == :~
60
+ is_not = true
61
+ elsif clazz == :is_a? || clazz == :kind_of? || clazz == :instance_of?
62
+ mode = :inherits
63
+ elsif clazz == :exists?
64
+ raise 'The \'exists\' quantifier is not yet supported.'
65
+ end
66
+ clazz = args.empty? ? nil : args.shift
67
+ end
68
+
69
+ if clazz == nil
70
+ clazz = Object
71
+ mode = :inherits
62
72
  end
63
73
 
64
- head_tag = nil
74
+ deftemplate = Core::DefTemplate.new clazz, mode
65
75
  atoms = []
66
76
  @when_counter += 1
67
-
68
- if args[0].kind_of?(Symbol)
69
- head_tag = args.shift
70
- @tags[head_tag] = @when_counter
71
- else
72
- head_tag = GeneratedTag.new
73
- @tags[head_tag] = @when_counter
74
- end
77
+ htag = Symbol === args[0] ? args.shift : GeneratedTag.new
78
+ head = Core::HeadAtom.new htag, deftemplate
79
+ @tags[htag] = @when_counter
75
80
 
76
81
  args.each do |arg|
77
82
  if arg.kind_of? Hash
78
83
  arg.each do |ab,tag|
79
84
  ab.tag = tag
80
- ab.clazz = clazz
85
+ ab.deftemplate = deftemplate
81
86
  @tags[tag] = @when_counter
82
87
  @methods[tag] = ab.name
83
88
  atoms.push ab.build_atom(@tags, @methods, @when_counter)
84
89
  end
85
90
  elsif arg.kind_of? AtomBuilder
86
91
  arg.tag = GeneratedTag.new
87
- arg.clazz = clazz
92
+ arg.deftemplate = deftemplate
88
93
  @methods[arg.tag] = arg.name
89
94
  atoms.push arg.build_atom(@tags, @methods, @when_counter)
90
95
  elsif arg == false
@@ -94,8 +99,13 @@ module Ruleby
94
99
  end
95
100
  end
96
101
 
97
- p = is_not ? Core::NotPattern.new(head_tag, clazz, atoms) :
98
- Core::ObjectPattern.new(head_tag, clazz, atoms)
102
+ if is_not
103
+ p = mode==:inherits ? Core::NotInheritsPattern.new(head, atoms) :
104
+ Core::NotPattern.new(head, atoms)
105
+ else
106
+ p = mode==:inherits ? Core::InheritsPattern.new(head, atoms) :
107
+ Core::ObjectPattern.new(head, atoms)
108
+ end
99
109
  @pattern = @pattern ? Core::AndPattern.new(@pattern, p) : p
100
110
 
101
111
  return nil
@@ -171,11 +181,11 @@ module Ruleby
171
181
  end
172
182
 
173
183
  class AtomBuilder
174
- attr_accessor :tag, :name, :bindings, :clazz, :block
184
+ attr_accessor :tag, :name, :bindings, :deftemplate, :block
175
185
 
176
186
  def initialize(name)
177
187
  @name = name
178
- @clazz = nil
188
+ @deftemplate = nil
179
189
  @tag = nil
180
190
  @bindings = []
181
191
  @block = lambda {|x| true}
@@ -188,6 +198,8 @@ module Ruleby
188
198
  end
189
199
 
190
200
  def ==(value)
201
+ @atom_type = :equals
202
+ @value = value
191
203
  create_block value, lambda {|x,y| x == y}, lambda {|x| x == value}; self
192
204
  end
193
205
 
@@ -209,22 +221,27 @@ module Ruleby
209
221
 
210
222
  def >=(value)
211
223
  create_block value, lambda {|x,y| x >= y}, lambda {|x| x >= value}; self
212
- end
224
+ end
213
225
 
214
- def build_atom(tags=nil,methods=nil,when_id=nil)
215
-
216
- raise 'Syntax error in Rules: class not set' unless @clazz
217
-
218
- return Core::TypeAtom.new(@tag, @clazz) unless tags || when_id
219
-
220
- return Core::PropertyAtom.new(@tag, @name, @clazz, &@block) if @bindings.empty?
226
+ def =~(value)
227
+ create_block value, lambda {|x,y| x =~ y}, lambda {|x| x =~ value}; self
228
+ end
229
+
230
+ def build_atom(tags,methods,when_id)
231
+ if @bindings.empty?
232
+ if @atom_type == :equals
233
+ return Core::EqualsAtom.new(@tag, @name, @deftemplate, @value)
234
+ else
235
+ return Core::PropertyAtom.new(@tag, @name, @deftemplate, &@block)
236
+ end
237
+ end
221
238
 
222
239
  if references_self?(tags,when_id)
223
240
  bind_methods = @bindings.collect{ |bb| methods[bb.tag] }
224
- Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@clazz,&@block)
241
+ Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate,&@block)
225
242
  else
226
243
  bind_tags = @bindings.collect{ |bb| bb.tag }
227
- Core::ReferenceAtom.new(@tag,@name,bind_tags,@clazz,&@block)
244
+ Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate,&@block)
228
245
  end
229
246
  end
230
247
 
@@ -13,7 +13,8 @@ module Ruleby
13
13
  module LeTigre
14
14
  class RulebookHelper
15
15
 
16
- def initialize(engine)
16
+ def initialize(engine, rulebook)
17
+ @rulebook = rulebook
17
18
  @engine = engine
18
19
  end
19
20
 
@@ -26,7 +27,7 @@ module Ruleby
26
27
 
27
28
  options = args[0].kind_of?(Hash) ? args.shift : {}
28
29
 
29
- pb = PatternParser.new
30
+ pb = PatternParser.new @rulebook
30
31
  pattern = pb.parse args
31
32
 
32
33
  rb = RuleBuilder.new name
@@ -81,14 +82,18 @@ module Ruleby
81
82
  @@method_error = "No #method in expression: "
82
83
 
83
84
  @@base_re = /(For each|\w*\s*exists\??|not\??)(.*)/
84
- @@where_re = /(.*) where (.*)/
85
+ @@mode_re = /( (is a|kind of|instance of) )(.*)/
86
+ @@where_re = /(.*)where (.*)/
85
87
  @@head_re = /(\w*)( as :(.*))?/
86
88
 
87
- @@method_re = /#(\w*|\d*\_*)*\??/
89
+ @@method_re = /#((\w|\d|\_)*)\??/
88
90
  @@bind_re = /#:(\w*|\d*\_*)*/
89
91
  @@and_re = /#&&/
90
92
  @@tag_re = /(.*) as :(.*)/
91
93
 
94
+ def initialize(rulebook)
95
+ @rulebook = rulebook
96
+ end
92
97
 
93
98
  def parse(lhs_strs)
94
99
  pattern = nil
@@ -104,6 +109,13 @@ module Ruleby
104
109
 
105
110
  raise 'The \'exists\' quantifier is not yet supported.' if base =~ /exists/
106
111
 
112
+ if tail =~ @@mode_re
113
+ mode = :inherits
114
+ tail = $3
115
+ else
116
+ mode = :equals
117
+ end
118
+
107
119
  # check if there is a where clause
108
120
  if tail =~ @@where_re
109
121
  head = $1.strip
@@ -114,27 +126,41 @@ module Ruleby
114
126
  end
115
127
 
116
128
  # match the class type and tag
117
- raise @@head_error unless head =~ @@head_re
118
- clazz = eval $1
119
- tag = $3 ? $3.to_sym : GeneratedTag.new
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::DefTemplate.new clazz, mode
140
+ head = Core::HeadAtom.new tag, deftemplate
120
141
 
121
142
  atoms = []
122
143
  atom_strs = tail ? tail.split(@@and_re) : []
123
144
  atom_strs.each do |a|
124
145
  # BUG we also need to pass in the head_tag with atoms!
125
- atoms.push parse_atom(a, clazz, atoms)
146
+ atoms.push parse_atom(a, deftemplate, atoms)
126
147
  end
127
-
128
- p = (base == 'not?' || base == 'not') ?
129
- Core::NotPattern.new(tag, clazz, atoms) :
130
- Core::ObjectPattern.new(tag, clazz, atoms)
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
+
131
157
  pattern = pattern ? Core::AndPattern.new(pattern, p) : p
132
158
  end
133
159
  return pattern
134
160
  end
135
161
 
136
162
  private
137
- def parse_atom(str, clazz, atoms)
163
+ def parse_atom(str, deftemplate, atoms)
138
164
  expression, tag = nil, nil
139
165
  if str =~ @@tag_re
140
166
  expression, tag = $1, $2.strip.to_sym
@@ -169,12 +195,12 @@ module Ruleby
169
195
  block = eval proc
170
196
 
171
197
  if bindings.empty?
172
- return Core::PropertyAtom.new(tag, method, clazz, &block)
198
+ return Core::PropertyAtom.new(tag, method, deftemplate, &block)
173
199
  elsif references_self?(bindings, atoms)
174
200
  bound_methods = resolve_bindings(bindings, atoms)
175
- return Core::SelfReferenceAtom.new(tag, method, bound_methods, clazz, &block)
201
+ return Core::SelfReferenceAtom.new(tag, method, bound_methods, deftemplate, &block)
176
202
  else
177
- return Core::ReferenceAtom.new(tag, method, bindings, clazz, &block)
203
+ return Core::ReferenceAtom.new(tag, method, bindings, deftemplate, &block)
178
204
  end
179
205
  end
180
206
 
@@ -200,7 +226,7 @@ module Ruleby
200
226
  bindings.each do |b|
201
227
  atoms.each do |a|
202
228
  if a.tag == b
203
- bound_methods.push a.name
229
+ bound_methods.push a.method
204
230
  end
205
231
  end
206
232
  end
@@ -15,6 +15,7 @@ module Ruleby
15
15
 
16
16
  include Ruleby
17
17
  def initialize(engine)
18
+ raise 'This DSL is deprecated'
18
19
  @engine = engine
19
20
  end
20
21
 
@@ -0,0 +1,23 @@
1
+
2
+ require 'ruleby'
3
+ require 'dsl/letigre'
4
+ module Ruleby
5
+ module YamlDsl
6
+ require 'yaml'
7
+ def self.load_rules(rules_yaml, engine)
8
+ ry = YAML::load(rules_yaml)
9
+ ry.each do |k,v|
10
+ if k =~ /_rule/
11
+ wh = v['when']
12
+ wh.gsub!('@', '#')
13
+ wh = wh.split(',')
14
+ th = v['then']
15
+ priority = v['priority']
16
+ th = "context engine -> #{th}"
17
+ r = LeTigre::RulebookHelper.new engine
18
+ r.rule k, { :priority => priority }, *wh, &th.to_proc
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -17,12 +17,23 @@ require 'dsl/steel'
17
17
  module Ruleby
18
18
  class Rulebook
19
19
  include Ruleby
20
- def initialize(engine)
20
+ def initialize(engine, session={})
21
21
  @engine = engine
22
+ @session = session
22
23
  end
23
24
 
24
25
  attr_reader :engine
25
-
26
+ attr_reader :session
27
+
28
+ def assert(fact)
29
+ @engine.assert fact
30
+ end
31
+ def retract(fact)
32
+ @engine.retract fact
33
+ end
34
+ def modify(fact)
35
+ @engine.modify fact
36
+ end
26
37
  def rule(*args, &block)
27
38
  unless args.empty?
28
39
  name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
@@ -40,7 +51,7 @@ module Ruleby
40
51
  r.rule name, *args, &block
41
52
  elsif args[i].kind_of? String
42
53
  # use letigre DSL
43
- r = LeTigre::RulebookHelper.new @engine
54
+ r = LeTigre::RulebookHelper.new @engine, self
44
55
  r.rule name, *args, &block
45
56
  else
46
57
  raise 'Rule format not recognized.'
@@ -71,6 +82,10 @@ module Ruleby
71
82
  def condition(&block)
72
83
  return lambda(&block)
73
84
  end
85
+
86
+ def __eval__(x)
87
+ eval(x)
88
+ end
74
89
  end
75
90
 
76
91
  class GeneratedTag
@@ -19,4 +19,41 @@ module Ruleby
19
19
  yield e if block_given?
20
20
  return e
21
21
  end
22
+ end
23
+
24
+ class String
25
+ unless ''.respond_to?(:to_proc)
26
+ def to_proc &block
27
+ params = []
28
+ expr = self
29
+ sections = expr.split(/\s*->\s*/m)
30
+ if sections.length > 1 then
31
+ eval sections.reverse!.inject { |e, p| "(Proc.new { |#{p.split(/\s/).join(', ')}| #{e} })" }, block && block.binding
32
+ elsif expr.match(/\b_\b/)
33
+ eval "Proc.new { |_| #{expr} }", block && block.binding
34
+ else
35
+ leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m)
36
+ rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m)
37
+ if leftSection || rightSection then
38
+ if (leftSection) then
39
+ params.push('$left')
40
+ expr = '$left' + expr
41
+ end
42
+ if (rightSection) then
43
+ params.push('$right')
44
+ expr = expr + '$right'
45
+ end
46
+ else
47
+ self.gsub(
48
+ /(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, ''
49
+ ).scan(
50
+ /([a-z_$][a-z_$\d]*)/i
51
+ ) do |v|
52
+ params.push(v) unless params.include?(v)
53
+ end
54
+ end
55
+ eval "Proc.new { |#{params.join(', ')}| #{expr} }", block && block.binding
56
+ end
57
+ end
58
+ end
22
59
  end