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 +11 -5
- data/lib/core/nodes.rb +8 -1
- data/lib/core/utils.rb +20 -0
- data/lib/dsl/ferrari.rb +153 -61
- data/lib/rule_helper.rb +56 -0
- data/lib/rulebook.rb +8 -42
- data/lib/ruleby.rb +1 -1
- data/tests/test.rb +1 -0
- metadata +3 -2
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.
|
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
|
-
|
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)
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
66
|
+
others[index] = child
|
40
67
|
end
|
68
|
+
index = index + 1
|
41
69
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
x.
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
data/lib/rule_helper.rb
ADDED
@@ -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
data/tests/test.rb
CHANGED
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.
|
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:
|
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
|