ruleby 0.6 → 0.7

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/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