ruleby 0.3 → 0.4

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.
@@ -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