ruleby 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/core/engine.rb CHANGED
@@ -23,6 +23,7 @@ module Ruleby
23
23
  attr_accessor :priority
24
24
  attr_accessor :name
25
25
  attr_reader :matches
26
+ attr_reader :proc
26
27
 
27
28
  def initialize(&block)
28
29
  @name = nil
@@ -30,8 +31,12 @@ module Ruleby
30
31
  @priority = 0
31
32
  end
32
33
 
33
- def fire(match)
34
- @proc.call(match)
34
+ def fire(match, engine=nil)
35
+ if @proc.arity == 2
36
+ @proc.call(match, engine)
37
+ else
38
+ @proc.call(match)
39
+ end
35
40
  end
36
41
 
37
42
  def ==(a2)
@@ -55,9 +60,9 @@ module Ruleby
55
60
  @used = false
56
61
  end
57
62
 
58
- def fire()
63
+ def fire(engine=nil)
59
64
  @used = true
60
- @action.fire @match
65
+ @action.fire @match, engine
61
66
  end
62
67
 
63
68
  def <=>(a2)
@@ -177,6 +182,7 @@ module Ruleby
177
182
  # instantiating it. Each rule engine has one inference engine, one rule set
178
183
  # and one working memory.
179
184
  class Engine
185
+
180
186
  def initialize(wm=WorkingMemory.new,cr=RulebyConflictResolver.new)
181
187
  @root = nil
182
188
  @working_memory = wm
@@ -231,7 +237,7 @@ module Ruleby
231
237
  agenda = @conflict_resolver.resolve agenda
232
238
  activation = agenda.pop
233
239
  used_agenda.push activation
234
- activation.fire
240
+ activation.fire self
235
241
  if @wm_altered
236
242
  agenda = @root.matches(false)
237
243
  @root.increment_counter
data/lib/core/nodes.rb CHANGED
@@ -414,7 +414,14 @@ module Ruleby
414
414
  # false, and the network traverse stops
415
415
  return
416
416
  end
417
- super if @atom.proc.call(val)
417
+ begin
418
+ super if @atom.proc.call(val)
419
+ rescue Exception => e
420
+ # There is a bug in Ruby MRI that goes away when we call print. Even if the following
421
+ # line of code is never executed at runtime. The problem does not exist in JRuby
422
+ print ''
423
+ raise ProcessInvocationError.new(e), e.message
424
+ end
418
425
  end
419
426
  end
420
427
 
data/lib/core/utils.rb CHANGED
@@ -18,6 +18,26 @@ module Ruleby
18
18
  class InitialFact
19
19
 
20
20
  end
21
+
22
+ # Appearently Ruby doesn't have any kind of Exception chaining. So this class will have
23
+ # fill the gap for Ruleby.
24
+ class ProcessInvocationError < StandardError
25
+ def initialize(root_cause)
26
+ @root_cause = root_cause
27
+ end
28
+
29
+ def backtrace
30
+ @root_cause.backtrace
31
+ end
32
+
33
+ def inspect
34
+ @root_cause.inspect
35
+ end
36
+
37
+ def to_s
38
+ @root_cause.to_s
39
+ end
40
+ end
21
41
 
22
42
  # This class is a wrapper for the context under which the network executes for
23
43
  # for a given fact. It is essentially a wrapper for a fact and a partial
data/lib/dsl/ferrari.rb CHANGED
@@ -4,9 +4,9 @@
4
4
  # modify it under the terms of the Ruby license defined in the
5
5
  # LICENSE.txt file.
6
6
  #
7
- # Copyright (c) 2009 Joe Kutner and Matt Smith. All rights reserved.
7
+ # Copyright (c) 2010 Joe Kutner and Matt Smith. All rights reserved.
8
8
  #
9
- # * Authors: Joe Kutner
9
+ # * Authors: Joe Kutner, Matt Smith
10
10
  #
11
11
 
12
12
  module Ruleby
@@ -21,76 +21,159 @@ module Ruleby
21
21
  def rule(name, *args, &block)
22
22
  options = args[0].kind_of?(Hash) ? args.shift : {}
23
23
 
24
- parse_containers(args, RulesContainer.new).build(name,options,@engine,&block)
25
- end
26
-
27
- private
28
- def parse_containers(args, container=AndContainer.new)
29
- or_builders = []
30
- and_container = AndContainer.new
31
- args.each do |arg|
32
- if arg.kind_of? Array
33
- and_container << PatternContainer.new(arg)
34
- elsif arg.kind_of? AndBuilder
35
- and_container << parse_containers(arg.conditions)
36
- elsif arg.kind_of? OrBuilder
37
- or_builders << arg
24
+ rules = Ruleby::Ferrari.parse_containers(args, RulesContainer.new).build(name,options,@engine,&block)
25
+ rules.each do |r|
26
+ engine.assert_rule(r)
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.parse_containers(args, container=Container(:and), parent=nil)
32
+ con = nil
33
+ if(container.kind_of?(RulesContainer))
34
+ con = Container.new(:and)
35
+ else
36
+ con = container
37
+ end
38
+ args.each do |arg|
39
+ if arg.kind_of? Array
40
+ con << PatternContainer.new(arg)
41
+ elsif arg.kind_of? AndBuilder
42
+ con << parse_containers(arg.conditions, Container.new(:and), container)
43
+ elsif arg.kind_of? OrBuilder
44
+ con << parse_containers(arg.conditions, Container.new(:or), container)
45
+ else
46
+ raise 'Invalid condition. Must be an OR, AND or an Array.'
47
+ end
48
+ end
49
+ if container.kind_of?(RulesContainer)
50
+ container << con
51
+ end
52
+ return container
53
+ end
54
+
55
+ class RulesContainer < Array
56
+ def transform_or(parent)
57
+ ors = []
58
+ others = []
59
+ permutations = 1
60
+ index = 0
61
+ parent.each do |child|
62
+ if(child.or?)
63
+ permutations *= child.size
64
+ ors << child
38
65
  else
39
- raise 'Invalid condition. Must be an OR, AND or an Array.'
66
+ others[index] = child
40
67
  end
68
+ index = index + 1
41
69
  end
42
-
43
- if or_builders.empty?
44
- container << and_container
45
- else
46
- while !or_builders.empty?
47
- or_builder = or_builders.pop
48
- parse_containers(or_builder.conditions, OrContainer.new).each do |or_container|
49
- or_container.each do |or_container_child|
50
- rule = AndContainer.new
51
- rule.push or_container_child
52
-
53
- or_builders.each do |sub_or_builder|
54
- parse_containers(sub_or_builder.conditions).each do |sub_or_container|
55
- rule.push *sub_or_container
56
- end
57
- end
58
-
59
- rule.push and_container
60
- container << rule
61
- end
62
- end
70
+ # set parent type to or and clear
71
+ parent.kind = :or
72
+ parent.clear
73
+ indexes = []
74
+ # initialize indexes
75
+ ors.each do |o|
76
+ indexes << 0
77
+ end
78
+ # create children
79
+ (1.upto(permutations)).each do |i|
80
+ and_container = Container.new(:and)
81
+
82
+ mod = 1
83
+ (ors.size - 1).downto(0) do |j|
84
+ and_container.insert(0,ors[j][indexes[j]])
85
+ if((i % mod) == 0)
86
+ indexes[j] = (indexes[j] + 1) % ors[j].size
87
+ end
88
+ mod *= ors[j].size
63
89
  end
64
- end
65
- return container
90
+
91
+ others.each_with_index do |other, k|
92
+ if others[k] != nil
93
+ and_container.insert(k, others[k])
94
+ end
95
+ end
96
+ # add child to parent
97
+ parent.push(and_container)
98
+ end
99
+ parent.uniq!
66
100
  end
67
- end
68
-
69
- class RulesContainer < Array
101
+
102
+ def handle_branching(container)
103
+ ands = []
104
+ container.each do |x|
105
+ if x.or?
106
+ x.each do |branch|
107
+ ands << branch
108
+ end
109
+ elsif x.and?
110
+ ands << x
111
+ else
112
+ new_and = Container.new(:and)
113
+ new_and << x
114
+ ands << new_and
115
+ end
116
+ end
117
+ return ands
118
+ end
119
+
70
120
  def build(name,options,engine,&block)
71
- self.each do |x|
72
- r = RuleBuilder.new name
73
- x.build r
74
- r.then(&block)
75
- r.priority = options[:priority] if options[:priority]
76
- engine.assert_rule(r.build_rule)
121
+ rules = []
122
+ self.each do |x|
123
+ x.process_tree do |c|
124
+ transform_or(c)
125
+ end
77
126
  end
127
+ handle_branching(self).each do |a|
128
+ rules << build_rule(name, a, options, &block)
129
+ end
130
+ return rules
131
+ end
132
+
133
+ def build_rule(name, container, options, &block)
134
+ r = RuleBuilder.new name
135
+ container.build r
136
+ r.then(&block)
137
+ r.priority = options[:priority] if options[:priority]
138
+ r.build_rule
78
139
  end
79
140
  end
80
141
 
81
- class AndContainer < Array
142
+
143
+ class Container < Array
144
+ attr_accessor :kind
145
+
146
+ def initialize(kind)
147
+ @kind = kind
148
+ end
149
+
82
150
  def build(builder)
151
+ if self.or?
152
+ # OrContainers are never built, they just contain containers that
153
+ # will be transformed into AndContainers.
154
+ raise 'Invalid Syntax'
155
+ end
83
156
  self.each do |x|
84
157
  x.build builder
85
158
  end
86
159
  end
87
- end
88
-
89
- class OrContainer < Array
90
- def build(builder)
91
- # OrContainers are never built, they just contain containers that
92
- # will be transformed into AndContainers.
93
- raise 'Invalid Syntax'
160
+
161
+ def or?
162
+ return kind == :or
163
+ end
164
+
165
+ def and?
166
+ return kind == :and
167
+ end
168
+
169
+ def process_tree(&block)
170
+ has_or_child = false
171
+ uniq!
172
+ each do |c|
173
+ has_or_child = true if (c.process_tree(&block) or c.or?)
174
+ end
175
+ yield(self) if (has_or_child)
176
+ return has_or_child
94
177
  end
95
178
  end
96
179
 
@@ -102,6 +185,19 @@ module Ruleby
102
185
  def build(builder)
103
186
  builder.when(*@condition)
104
187
  end
188
+
189
+ def process_tree
190
+ # there is no tree to process
191
+ false
192
+ end
193
+
194
+ def or?
195
+ false
196
+ end
197
+
198
+ def and?
199
+ false
200
+ end
105
201
  end
106
202
 
107
203
  class RuleBuilder
@@ -288,10 +384,6 @@ module Ruleby
288
384
  create_block value, lambda {|x,y| x >= y}, lambda {|x| x >= value}; self
289
385
  end
290
386
 
291
- def =~(value)
292
- create_block value, lambda {|x,y| x =~ y}, lambda {|x| x =~ value}; self
293
- end
294
-
295
387
  def build_atoms(tags,methods,when_id)
296
388
  atoms = @child_atom_builders.map { |atom_builder|
297
389
  tags[atom_builder.tag] = when_id
@@ -0,0 +1,56 @@
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
+
12
+ require 'core/engine'
13
+
14
+ module Ruleby
15
+ module RuleHelper
16
+ def rule(*args, &block)
17
+ name = nil
18
+ unless args.empty?
19
+ name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
20
+ end
21
+ options = args[0].kind_of?(Hash) ? args.shift : {}
22
+
23
+ rules = Ruleby::Ferrari.parse_containers(args, Ruleby::Ferrari::RulesContainer.new).build(name,options,@engine,&block)
24
+ rules
25
+ end
26
+
27
+ def m
28
+ Ruleby::Ferrari::MethodBuilder.new
29
+ end
30
+
31
+ def method
32
+ m
33
+ end
34
+
35
+ def b(variable_name)
36
+ Ruleby::Ferrari::BindingBuilder.new(variable_name)
37
+ end
38
+
39
+ def c(&block)
40
+ return lambda(&block)
41
+ end
42
+
43
+ def OR(*args)
44
+ Ruleby::Ferrari::OrBuilder.new args
45
+ end
46
+
47
+ def AND(*args)
48
+ Ruleby::Ferrari::AndBuilder.new args
49
+ end
50
+
51
+ def __eval__(x)
52
+ eval(x)
53
+ end
54
+
55
+ end
56
+ end
data/lib/rulebook.rb CHANGED
@@ -10,6 +10,7 @@
10
10
  #
11
11
 
12
12
  require 'ruleby'
13
+ require 'rule_helper'
13
14
  require 'dsl/ferrari'
14
15
  require 'dsl/letigre'
15
16
  require 'dsl/steel'
@@ -17,6 +18,7 @@ require 'dsl/steel'
17
18
  module Ruleby
18
19
  class Rulebook
19
20
  include Ruleby
21
+ include Ruleby::RuleHelper
20
22
  def initialize(engine, &block)
21
23
  @engine = engine
22
24
  yield self if block_given?
@@ -57,66 +59,30 @@ module Ruleby
57
59
  end
58
60
  end
59
61
  end
60
-
61
- def m
62
- Ruleby::Ferrari::MethodBuilder.new
63
- end
64
-
65
- def method
66
- m
67
- end
68
-
69
- def b(variable_name)
70
- Ruleby::Ferrari::BindingBuilder.new(variable_name)
71
- end
72
-
73
- def binding(variable_name)
74
- b variable_name
75
- end
76
-
77
- def c(&block)
78
- return lambda(&block)
79
- end
80
-
81
- def condition(&block)
82
- return lambda(&block)
83
- end
84
-
85
- def OR(*args)
86
- Ruleby::Ferrari::OrBuilder.new args
87
- end
88
-
89
- def AND(*args)
90
- Ruleby::Ferrari::AndBuilder.new args
91
- end
92
-
93
- def __eval__(x)
94
- eval(x)
95
- end
96
- end
62
+ end
97
63
 
98
64
  class GeneratedTag
99
65
  # this counter is incremented for each UniqueTag created, and is
100
66
  # appended to the end of the unique_seed in order to create a
101
67
  # string that is unique for each instance of this class.
102
68
  @@tag_counter = 0
103
-
69
+
104
70
  # every generated tag will be prefixed with this string
105
71
  @@unique_seed = 'unique_seed'
106
-
72
+
107
73
  def initialize()
108
74
  @@tag_counter += 1
109
75
  @tag = @@unique_seed + @@tag_counter.to_s
110
76
  end
111
-
77
+
112
78
  attr_reader:tag_counter
113
79
  attr_reader:unique_seed
114
80
  attr_reader:tag
115
-
81
+
116
82
  def ==(ut)
117
83
  return ut && ut.kind_of?(GeneratedTag) && @tag == ut.tag
118
84
  end
119
-
85
+
120
86
  def to_s
121
87
  return @tag.to_s
122
88
  end
data/lib/ruleby.rb CHANGED
@@ -18,7 +18,7 @@ module Ruleby
18
18
  e = Core::Engine.new
19
19
  yield e if block_given?
20
20
  return e
21
- end
21
+ end
22
22
  end
23
23
 
24
24
  class String
data/tests/test.rb CHANGED
@@ -17,3 +17,4 @@ require 'assert_facts'
17
17
  require 'not_patterns'
18
18
  require 'or_patterns'
19
19
  require 'join_nodes'
20
+ require 'nil'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruleby
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.6"
4
+ version: "0.7"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Kutner
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-11-23 00:00:00 -06:00
13
+ date: 2010-08-27 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -36,6 +36,7 @@ files:
36
36
  - lib/dsl/ferrari.rb
37
37
  - lib/dsl/letigre.rb
38
38
  - lib/dsl/steel.rb
39
+ - lib/rule_helper.rb
39
40
  - lib/rulebook.rb
40
41
  - lib/ruleby.rb
41
42
  has_rdoc: true