ruleby 0.7 → 0.8.b1
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/atoms.rb +2 -2
- data/lib/core/engine.rb +272 -268
- data/lib/core/nodes.rb +95 -7
- data/lib/core/patterns.rb +118 -115
- data/lib/dsl/ferrari.rb +45 -30
- data/lib/dsl/letigre.rb +1 -1
- metadata +37 -26
data/lib/core/atoms.rb
CHANGED
|
@@ -136,7 +136,7 @@ module Ruleby
|
|
|
136
136
|
# attribute represents a Class type, and the mode defines whether the head
|
|
137
137
|
# will match only class that are exactly a particular type, or if it will
|
|
138
138
|
# match classes that inherit that type also.
|
|
139
|
-
class
|
|
139
|
+
class Template
|
|
140
140
|
attr_reader :clazz
|
|
141
141
|
attr_reader :mode
|
|
142
142
|
|
|
@@ -146,7 +146,7 @@ module Ruleby
|
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
def ==(df)
|
|
149
|
-
|
|
149
|
+
Template === df && df.clazz == @clazz && df.mode == @mode
|
|
150
150
|
end
|
|
151
151
|
end
|
|
152
152
|
|
data/lib/core/engine.rb
CHANGED
|
@@ -1,269 +1,273 @@
|
|
|
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/atoms'
|
|
13
|
-
require 'core/patterns'
|
|
14
|
-
require 'core/utils'
|
|
15
|
-
require 'core/nodes'
|
|
16
|
-
|
|
17
|
-
module Ruleby
|
|
18
|
-
module Core
|
|
19
|
-
|
|
20
|
-
# An action is a wrapper for a code block that will be executed if a rule is
|
|
21
|
-
# satisfied.
|
|
22
|
-
class Action
|
|
23
|
-
attr_accessor :priority
|
|
24
|
-
attr_accessor :name
|
|
25
|
-
attr_reader :matches
|
|
26
|
-
attr_reader :proc
|
|
27
|
-
|
|
28
|
-
def initialize(&block)
|
|
29
|
-
@name = nil
|
|
30
|
-
@proc = Proc.new(&block) if block_given?
|
|
31
|
-
@priority = 0
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def fire(match, engine=nil)
|
|
35
|
-
if @proc.arity == 2
|
|
36
|
-
@proc.call(match, engine)
|
|
37
|
-
else
|
|
38
|
-
@proc.call(match)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def ==(a2)
|
|
43
|
-
return @name == a2.name
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# An activation is an action/match pair that is executed if a rule is matched.
|
|
48
|
-
# It also contains metadata that can be used for conflict resolution if two
|
|
49
|
-
# rules are satisfied by the same fact.
|
|
50
|
-
class Activation
|
|
51
|
-
attr_reader :action, :match
|
|
52
|
-
attr_accessor :counter, :used
|
|
53
|
-
|
|
54
|
-
def initialize(action, match, counter=0)
|
|
55
|
-
@action = action
|
|
56
|
-
@match = match
|
|
57
|
-
@match.recency.sort!
|
|
58
|
-
@match.recency.reverse!
|
|
59
|
-
@counter = counter
|
|
60
|
-
@used = false
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def fire(engine=nil)
|
|
64
|
-
@used = true
|
|
65
|
-
@action.fire @match, engine
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def <=>(a2)
|
|
69
|
-
return @counter <=> a2.counter if @counter != a2.counter
|
|
70
|
-
return @action.priority <=> a2.action.priority if @action.priority != a2.action.priority
|
|
71
|
-
|
|
72
|
-
# NOTE in order for this to work, the array must be reverse sorted
|
|
73
|
-
i = 0; while @match.recency[i] == a2.match.recency[i] && i < @match.recency.size-1 && i < a2.match.recency.size-1
|
|
74
|
-
i += 1
|
|
75
|
-
end
|
|
76
|
-
return @match.recency[i] <=> a2.match.recency[i]
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def ==(a2)
|
|
80
|
-
return a2 != nil && @action == a2.action && @match == a2.match
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def to_s
|
|
84
|
-
return "[#{@action.name}-#{object_id}|#{@counter}|#{@action.priority}|#{@match.recency.join(',')}|#{@match.to_s}] "
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
class Rule
|
|
89
|
-
attr_accessor :pattern
|
|
90
|
-
attr_reader :action, :name, :priority
|
|
91
|
-
|
|
92
|
-
def initialize(name, pattern=nil, action=nil, priority=0)
|
|
93
|
-
@name = name
|
|
94
|
-
@pattern = pattern
|
|
95
|
-
@action = action
|
|
96
|
-
@priority = priority
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def priority=(p)
|
|
100
|
-
@priority = p
|
|
101
|
-
@action.priority = @priority
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# A fact is an object that is stored in working memory. The rules in the
|
|
106
|
-
# system will either look for the existence or absence of particular facts.
|
|
107
|
-
class Fact
|
|
108
|
-
attr :token, true
|
|
109
|
-
attr :recency, true
|
|
110
|
-
attr_reader :object
|
|
111
|
-
|
|
112
|
-
def initialize(object, token)
|
|
113
|
-
@token = token
|
|
114
|
-
@object = object
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def id
|
|
118
|
-
return object.object_id
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def ==(fact)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@facts
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
@root
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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/atoms'
|
|
13
|
+
require 'core/patterns'
|
|
14
|
+
require 'core/utils'
|
|
15
|
+
require 'core/nodes'
|
|
16
|
+
|
|
17
|
+
module Ruleby
|
|
18
|
+
module Core
|
|
19
|
+
|
|
20
|
+
# An action is a wrapper for a code block that will be executed if a rule is
|
|
21
|
+
# satisfied.
|
|
22
|
+
class Action
|
|
23
|
+
attr_accessor :priority
|
|
24
|
+
attr_accessor :name
|
|
25
|
+
attr_reader :matches
|
|
26
|
+
attr_reader :proc
|
|
27
|
+
|
|
28
|
+
def initialize(&block)
|
|
29
|
+
@name = nil
|
|
30
|
+
@proc = Proc.new(&block) if block_given?
|
|
31
|
+
@priority = 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fire(match, engine=nil)
|
|
35
|
+
if @proc.arity == 2
|
|
36
|
+
@proc.call(match, engine)
|
|
37
|
+
else
|
|
38
|
+
@proc.call(match)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(a2)
|
|
43
|
+
return @name == a2.name
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# An activation is an action/match pair that is executed if a rule is matched.
|
|
48
|
+
# It also contains metadata that can be used for conflict resolution if two
|
|
49
|
+
# rules are satisfied by the same fact.
|
|
50
|
+
class Activation
|
|
51
|
+
attr_reader :action, :match
|
|
52
|
+
attr_accessor :counter, :used
|
|
53
|
+
|
|
54
|
+
def initialize(action, match, counter=0)
|
|
55
|
+
@action = action
|
|
56
|
+
@match = match
|
|
57
|
+
@match.recency.sort!
|
|
58
|
+
@match.recency.reverse!
|
|
59
|
+
@counter = counter
|
|
60
|
+
@used = false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fire(engine=nil)
|
|
64
|
+
@used = true
|
|
65
|
+
@action.fire @match, engine
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def <=>(a2)
|
|
69
|
+
return @counter <=> a2.counter if @counter != a2.counter
|
|
70
|
+
return @action.priority <=> a2.action.priority if @action.priority != a2.action.priority
|
|
71
|
+
|
|
72
|
+
# NOTE in order for this to work, the array must be reverse sorted
|
|
73
|
+
i = 0; while @match.recency[i] == a2.match.recency[i] && i < @match.recency.size-1 && i < a2.match.recency.size-1
|
|
74
|
+
i += 1
|
|
75
|
+
end
|
|
76
|
+
return @match.recency[i] <=> a2.match.recency[i]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ==(a2)
|
|
80
|
+
return a2 != nil && @action == a2.action && @match == a2.match
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
return "[#{@action.name}-#{object_id}|#{@counter}|#{@action.priority}|#{@match.recency.join(',')}|#{@match.to_s}] "
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Rule
|
|
89
|
+
attr_accessor :pattern
|
|
90
|
+
attr_reader :action, :name, :priority
|
|
91
|
+
|
|
92
|
+
def initialize(name, pattern=nil, action=nil, priority=0)
|
|
93
|
+
@name = name
|
|
94
|
+
@pattern = pattern
|
|
95
|
+
@action = action
|
|
96
|
+
@priority = priority
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def priority=(p)
|
|
100
|
+
@priority = p
|
|
101
|
+
@action.priority = @priority
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# A fact is an object that is stored in working memory. The rules in the
|
|
106
|
+
# system will either look for the existence or absence of particular facts.
|
|
107
|
+
class Fact
|
|
108
|
+
attr :token, true
|
|
109
|
+
attr :recency, true
|
|
110
|
+
attr_reader :object
|
|
111
|
+
|
|
112
|
+
def initialize(object, token)
|
|
113
|
+
@token = token
|
|
114
|
+
@object = object
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def id
|
|
118
|
+
return object.object_id
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def ==(fact)
|
|
122
|
+
if fact.is_a? Fact
|
|
123
|
+
fact != nil && fact.id == id
|
|
124
|
+
else
|
|
125
|
+
fact != nil && fact.object_id == id
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def to_s
|
|
130
|
+
"[Fact |#{@recency}|#{@object.to_s}]"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# A conflict resolver is used to order activations that become active at the
|
|
135
|
+
# same time. The default implementation sorts the agenda based on the
|
|
136
|
+
# properties of the activation.
|
|
137
|
+
class RulebyConflictResolver
|
|
138
|
+
def resolve(agenda)
|
|
139
|
+
return agenda.sort
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The working memory is a container for all the facts in the system. The
|
|
144
|
+
# inference engine will compare these facts with the rules to produce some
|
|
145
|
+
# outcomes.
|
|
146
|
+
class WorkingMemory
|
|
147
|
+
attr_reader :facts
|
|
148
|
+
|
|
149
|
+
def initialize
|
|
150
|
+
@recency = 0
|
|
151
|
+
@facts = Array.new
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def each_fact
|
|
155
|
+
@facts.each do |f|
|
|
156
|
+
yield(f)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def assert_fact(fact)
|
|
161
|
+
raise 'The fact asserted cannot be nil!' unless fact.object
|
|
162
|
+
if (fact.token == :plus)
|
|
163
|
+
fact.recency = @recency
|
|
164
|
+
@recency += 1
|
|
165
|
+
@facts.push fact
|
|
166
|
+
return fact
|
|
167
|
+
else #if (fact.token == :minus)
|
|
168
|
+
i = @facts.index(fact)
|
|
169
|
+
raise 'The fact to remove does not exist!' unless i
|
|
170
|
+
existing_fact = @facts[i]
|
|
171
|
+
@facts.delete_at(i)
|
|
172
|
+
existing_fact.token = fact.token
|
|
173
|
+
return existing_fact
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def print
|
|
178
|
+
puts 'WORKING MEMORY:'
|
|
179
|
+
@facts.each do |fact|
|
|
180
|
+
puts " #{fact.object} - #{fact.id} - #{fact.recency}"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# This is the core class of the library. A new rule engine is created by
|
|
186
|
+
# instantiating it. Each rule engine has one inference engine, one rule set
|
|
187
|
+
# and one working memory.
|
|
188
|
+
class Engine
|
|
189
|
+
|
|
190
|
+
def initialize(wm=WorkingMemory.new,cr=RulebyConflictResolver.new)
|
|
191
|
+
@root = nil
|
|
192
|
+
@working_memory = wm
|
|
193
|
+
@conflict_resolver = cr
|
|
194
|
+
@wm_altered = false
|
|
195
|
+
assert InitialFact.new
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def facts
|
|
199
|
+
@working_memory.facts.collect{|f| f.object}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# This method id called to add a new fact to working memory
|
|
203
|
+
def assert(object,&block)
|
|
204
|
+
@wm_altered = true
|
|
205
|
+
fact_helper(object,:plus,&block)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# This method is called to remove an existing fact from working memory
|
|
209
|
+
def retract(object,&block)
|
|
210
|
+
@wm_altered = true
|
|
211
|
+
fact_helper(object,:minus,&block)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# This method is called to alter an existing fact. It is essentially a
|
|
215
|
+
# retract followed by an assert.
|
|
216
|
+
def modify(object,&block)
|
|
217
|
+
retract(object,&block)
|
|
218
|
+
assert(object,&block)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def retrieve(c)
|
|
222
|
+
facts.select {|f| f.kind_of?(c)}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# This method adds a new rule to the system.
|
|
226
|
+
def assert_rule(rule)
|
|
227
|
+
if @root == nil
|
|
228
|
+
@root = RootNode.new(@working_memory)
|
|
229
|
+
@root.reset_counter
|
|
230
|
+
end
|
|
231
|
+
@root.assert_rule rule
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# This method executes the activations that were generated by the rules
|
|
235
|
+
# that match facts in working memory.
|
|
236
|
+
def match(agenda=nil, used_agenda=[])
|
|
237
|
+
if @root
|
|
238
|
+
@root.reset_counter
|
|
239
|
+
agenda = @root.matches unless agenda
|
|
240
|
+
while (agenda.length > 0)
|
|
241
|
+
agenda = @conflict_resolver.resolve agenda
|
|
242
|
+
activation = agenda.pop
|
|
243
|
+
used_agenda.push activation
|
|
244
|
+
activation.fire self
|
|
245
|
+
if @wm_altered
|
|
246
|
+
agenda = @root.matches(false)
|
|
247
|
+
@root.increment_counter
|
|
248
|
+
@wm_altered = false
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def print
|
|
255
|
+
@working_memory.print
|
|
256
|
+
@root.print
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
def fact_helper(object, sign=:plus, &block)
|
|
261
|
+
f = Core::Fact.new object, sign
|
|
262
|
+
yield f if block_given?
|
|
263
|
+
assert_fact f
|
|
264
|
+
f
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def assert_fact(fact)
|
|
268
|
+
wm_fact = @working_memory.assert_fact fact
|
|
269
|
+
@root.assert_fact wm_fact if @root != nil
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
269
273
|
end
|
data/lib/core/nodes.rb
CHANGED
|
@@ -200,7 +200,11 @@ module Ruleby
|
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
def create_bridge_node(pattern)
|
|
203
|
-
|
|
203
|
+
if pattern.kind_of?(CollectPattern)
|
|
204
|
+
CollectNode.new(pattern)
|
|
205
|
+
else
|
|
206
|
+
BridgeNode.new(pattern)
|
|
207
|
+
end
|
|
204
208
|
end
|
|
205
209
|
|
|
206
210
|
def create_property_node(atom,forked)
|
|
@@ -483,12 +487,14 @@ module Ruleby
|
|
|
483
487
|
# network, or to the terminal nodes. It creates a partial match from the
|
|
484
488
|
# pattern and atoms above it in the network. Thus, there is one bridge node
|
|
485
489
|
# for each pattern (assuming they aren't shared).
|
|
486
|
-
class
|
|
490
|
+
class BaseBridgeNode < ParentNode
|
|
487
491
|
def initialize(pattern)
|
|
488
492
|
super()
|
|
489
493
|
@pattern = pattern
|
|
490
494
|
end
|
|
491
|
-
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
class BridgeNode < BaseBridgeNode
|
|
492
498
|
def propagate_assert(fact)
|
|
493
499
|
# create the partial match
|
|
494
500
|
mr = MatchResult.new
|
|
@@ -505,7 +511,72 @@ module Ruleby
|
|
|
505
511
|
end
|
|
506
512
|
super(MatchContext.new(fact,mr))
|
|
507
513
|
end
|
|
508
|
-
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
class CollectNode < BaseBridgeNode
|
|
517
|
+
def initialize(pattern)
|
|
518
|
+
super
|
|
519
|
+
@collection_memory = Fact.new([], :internal)
|
|
520
|
+
@should_modify = false
|
|
521
|
+
# not really sure what to do about this. might just need to handle nil's
|
|
522
|
+
# using a puts a limit on how many facts can be asserted before this feature breaks
|
|
523
|
+
# it might be best to get a handle to WorkingMemory in order to get the real
|
|
524
|
+
# @collection_memory.recency = 0
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def retract(fact)
|
|
528
|
+
propagate_retract(@collection_memory)
|
|
529
|
+
propagate_assert(fact) do
|
|
530
|
+
@collection_memory.object.delete_if {|a| a == fact.object}
|
|
531
|
+
@should_modify = false
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def propagate_modify(context, out_nodes=@out_nodes)
|
|
536
|
+
out_nodes.each do |out_node|
|
|
537
|
+
if out_node.is_a?(TerminalNode)
|
|
538
|
+
out_node.modify(context)
|
|
539
|
+
else
|
|
540
|
+
raise "You can't join to :collect patterns yet!"
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def propagate_assert(fact)
|
|
546
|
+
if block_given?
|
|
547
|
+
yield fact
|
|
548
|
+
else
|
|
549
|
+
@collection_memory.object << fact
|
|
550
|
+
@collection_memory.recency = fact.recency
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
if @should_modify
|
|
554
|
+
propagate_modify(MatchContext.new(@collection_memory, create_match_result))
|
|
555
|
+
else
|
|
556
|
+
if @collection_memory.object.size > 0
|
|
557
|
+
@should_modify = true
|
|
558
|
+
super(MatchContext.new(@collection_memory, create_match_result))
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
private
|
|
564
|
+
|
|
565
|
+
def create_match_result()
|
|
566
|
+
# create the partial match
|
|
567
|
+
mr = MatchResult.new
|
|
568
|
+
mr.is_match = true
|
|
569
|
+
mr.recency.push @collection_memory.recency
|
|
570
|
+
|
|
571
|
+
@pattern.atoms.each do |atom|
|
|
572
|
+
mr.fact_hash[atom.tag] = @collection_memory.id
|
|
573
|
+
if atom == @pattern.head
|
|
574
|
+
mr[atom.tag] = @collection_memory.object
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
mr
|
|
578
|
+
end
|
|
579
|
+
end
|
|
509
580
|
|
|
510
581
|
# This class is used to plug nodes into the left input of a two-input JoinNode
|
|
511
582
|
class LeftAdapterNode < ParentNode
|
|
@@ -549,7 +620,7 @@ module Ruleby
|
|
|
549
620
|
end
|
|
550
621
|
end
|
|
551
622
|
end
|
|
552
|
-
|
|
623
|
+
|
|
553
624
|
# This class is a two-input node that is used to create a cross-product of the
|
|
554
625
|
# two network branches above it. It keeps a memory of the left and right
|
|
555
626
|
# inputs and compares new facts to each. These nodes make up what is called
|
|
@@ -740,13 +811,30 @@ module Ruleby
|
|
|
740
811
|
@rule = rule
|
|
741
812
|
@activations = MultiHash.new
|
|
742
813
|
end
|
|
743
|
-
attr_reader:activations
|
|
744
|
-
|
|
814
|
+
#attr_reader:activations
|
|
815
|
+
|
|
816
|
+
def activations
|
|
817
|
+
@activations
|
|
818
|
+
end
|
|
819
|
+
|
|
745
820
|
def assert(context)
|
|
746
821
|
match = context.match
|
|
747
822
|
a = Activation.new(@rule.action, match, @@counter)
|
|
748
823
|
@activations.add match.fact_ids, a
|
|
749
824
|
end
|
|
825
|
+
|
|
826
|
+
def modify(context)
|
|
827
|
+
found = false
|
|
828
|
+
@activations.each do |id, v|
|
|
829
|
+
if context.match.fact_ids.sort == id.sort
|
|
830
|
+
v.replace(context.fact)
|
|
831
|
+
found = true
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
if !found
|
|
835
|
+
assert(context)
|
|
836
|
+
end
|
|
837
|
+
end
|
|
750
838
|
|
|
751
839
|
def retract(fact)
|
|
752
840
|
@activations.remove fact.id
|
data/lib/core/patterns.rb
CHANGED
|
@@ -1,116 +1,119 @@
|
|
|
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
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
module Ruleby
|
|
13
|
-
module Core
|
|
14
|
-
|
|
15
|
-
class Pattern
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# This class represents a pattern that is looking for the existence of some
|
|
19
|
-
# object. It contains a list of 'atoms' that represent the properties of
|
|
20
|
-
# the class that we are looking for.
|
|
21
|
-
class ObjectPattern < Pattern
|
|
22
|
-
attr_reader :atoms
|
|
23
|
-
|
|
24
|
-
def initialize(head, atoms)
|
|
25
|
-
@atoms = [head] + atoms
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def head
|
|
29
|
-
@atoms[0]
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def ==(pattern)
|
|
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
|
|
40
|
-
end
|
|
41
|
-
return true
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
return false
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def to_s
|
|
48
|
-
return '(' + @atoms.join('|') + ')'
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
class InheritsPattern < ObjectPattern
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
module Ruleby
|
|
13
|
+
module Core
|
|
14
|
+
|
|
15
|
+
class Pattern
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# This class represents a pattern that is looking for the existence of some
|
|
19
|
+
# object. It contains a list of 'atoms' that represent the properties of
|
|
20
|
+
# the class that we are looking for.
|
|
21
|
+
class ObjectPattern < Pattern
|
|
22
|
+
attr_reader :atoms
|
|
23
|
+
|
|
24
|
+
def initialize(head, atoms)
|
|
25
|
+
@atoms = [head] + atoms
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def head
|
|
29
|
+
@atoms[0]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(pattern)
|
|
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
|
|
40
|
+
end
|
|
41
|
+
return true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
return false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
return '(' + @atoms.join('|') + ')'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class InheritsPattern < ObjectPattern
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class CollectPattern < ObjectPattern
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# This class represents a pattern that is looking for the absence of some
|
|
59
|
+
# object (rather than the existence of). In all respects, it is the same as
|
|
60
|
+
# an ObjectPattern, but it is handled differently by the inference engine.
|
|
61
|
+
class NotPattern < ObjectPattern
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class NotInheritsPattern < InheritsPattern
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A composite pattern represents a logical conjunction of two patterns. The
|
|
68
|
+
# inference engine interprets this differently from an ObjectPattern because
|
|
69
|
+
# it simply aggregates patterns.
|
|
70
|
+
class CompositePattern < Pattern
|
|
71
|
+
|
|
72
|
+
attr_reader :left_pattern
|
|
73
|
+
attr_reader :right_pattern
|
|
74
|
+
|
|
75
|
+
def initialize(left_pattern, right_pattern)
|
|
76
|
+
@left_pattern = left_pattern
|
|
77
|
+
@right_pattern = right_pattern
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def atoms
|
|
81
|
+
atoms = []
|
|
82
|
+
atoms.push @left_pattern.atoms
|
|
83
|
+
atoms.push @right_pattern.atoms
|
|
84
|
+
return atoms
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class AndPattern < CompositePattern
|
|
89
|
+
|
|
90
|
+
def initialize(left_pattern, right_pattern)
|
|
91
|
+
super(left_pattern, right_pattern)
|
|
92
|
+
@head = :and
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class OrPattern < CompositePattern
|
|
98
|
+
|
|
99
|
+
def initialize(left_pattern, right_pattern)
|
|
100
|
+
super(left_pattern, right_pattern)
|
|
101
|
+
@head = :or
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class InitialFactPattern < ObjectPattern
|
|
107
|
+
def initialize
|
|
108
|
+
deftemplate = Template.new InitialFact, :equals
|
|
109
|
+
htag = GeneratedTag.new
|
|
110
|
+
head = HeadAtom.new htag, deftemplate
|
|
111
|
+
super(head, [])
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class PatternFactory
|
|
116
|
+
# TODO add some convenience methods for creating patterns
|
|
117
|
+
end
|
|
118
|
+
end
|
|
116
119
|
end
|
data/lib/dsl/ferrari.rb
CHANGED
|
@@ -216,12 +216,15 @@ module Ruleby
|
|
|
216
216
|
def when(*args)
|
|
217
217
|
clazz = AtomBuilder === args[0] ? nil : args.shift
|
|
218
218
|
is_not = false
|
|
219
|
+
is_collect = false
|
|
219
220
|
mode = :equals
|
|
220
221
|
while clazz.is_a? Symbol
|
|
221
222
|
if clazz == :not || clazz == :~
|
|
222
223
|
is_not = true
|
|
223
224
|
elsif clazz == :is_a? || clazz == :kind_of? || clazz == :instance_of?
|
|
224
225
|
mode = :inherits
|
|
226
|
+
elsif clazz == :collect
|
|
227
|
+
is_collect = true
|
|
225
228
|
elsif clazz == :exists?
|
|
226
229
|
raise 'The \'exists\' quantifier is not yet supported.'
|
|
227
230
|
end
|
|
@@ -233,7 +236,7 @@ module Ruleby
|
|
|
233
236
|
mode = :inherits
|
|
234
237
|
end
|
|
235
238
|
|
|
236
|
-
deftemplate = Core::
|
|
239
|
+
deftemplate = Core::Template.new clazz, mode
|
|
237
240
|
atoms = []
|
|
238
241
|
@when_counter += 1
|
|
239
242
|
htag = Symbol === args[0] ? args.shift : GeneratedTag.new
|
|
@@ -259,14 +262,15 @@ module Ruleby
|
|
|
259
262
|
else
|
|
260
263
|
raise "Invalid condition: #{arg}"
|
|
261
264
|
end
|
|
262
|
-
end
|
|
265
|
+
end
|
|
263
266
|
|
|
264
267
|
if is_not
|
|
265
268
|
p = mode==:inherits ? Core::NotInheritsPattern.new(head, atoms) :
|
|
266
269
|
Core::NotPattern.new(head, atoms)
|
|
267
270
|
else
|
|
268
271
|
p = mode==:inherits ? Core::InheritsPattern.new(head, atoms) :
|
|
269
|
-
Core::
|
|
272
|
+
is_collect ? Core::CollectPattern.new(head, atoms) :
|
|
273
|
+
Core::ObjectPattern.new(head, atoms)
|
|
270
274
|
end
|
|
271
275
|
@pattern = @pattern ? Core::AndPattern.new(@pattern, p) : p
|
|
272
276
|
end
|
|
@@ -293,6 +297,8 @@ module Ruleby
|
|
|
293
297
|
|
|
294
298
|
class MethodBuilder
|
|
295
299
|
public_instance_methods.each do |m|
|
|
300
|
+
# maybe we shouldn't be undefing object_id. What are the implications? Can we make object_id a
|
|
301
|
+
# pass through to the underlying object's object_id?
|
|
296
302
|
a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
|
|
297
303
|
undef_method m.to_sym unless a.include? m.to_sym
|
|
298
304
|
end
|
|
@@ -318,31 +324,31 @@ module Ruleby
|
|
|
318
324
|
@tag = tag
|
|
319
325
|
@method = method
|
|
320
326
|
end
|
|
321
|
-
|
|
327
|
+
|
|
322
328
|
def +(arg)
|
|
323
329
|
raise 'Cannot use operators in short-hand mode!'
|
|
324
330
|
end
|
|
325
|
-
|
|
331
|
+
|
|
326
332
|
def -(arg)
|
|
327
333
|
raise 'Cannot use operators in short-hand mode!'
|
|
328
334
|
end
|
|
329
|
-
|
|
335
|
+
|
|
330
336
|
def /(arg)
|
|
331
337
|
raise 'Cannot use operators in short-hand mode!'
|
|
332
338
|
end
|
|
333
|
-
|
|
339
|
+
|
|
334
340
|
def *(arg)
|
|
335
341
|
raise 'Cannot use operators in short-hand mode!'
|
|
336
342
|
end
|
|
337
|
-
|
|
343
|
+
|
|
338
344
|
def to_s
|
|
339
345
|
"BindingBuilder @tag=#{@tag}, @method=#{@method}"
|
|
340
346
|
end
|
|
341
347
|
end
|
|
342
|
-
|
|
348
|
+
|
|
343
349
|
class AtomBuilder
|
|
344
350
|
attr_accessor :tag, :name, :bindings, :deftemplate, :block
|
|
345
|
-
|
|
351
|
+
|
|
346
352
|
def initialize(method_id)
|
|
347
353
|
@name = method_id
|
|
348
354
|
@deftemplate = nil
|
|
@@ -351,39 +357,48 @@ module Ruleby
|
|
|
351
357
|
@block = lambda {|x| true}
|
|
352
358
|
@child_atom_builders = []
|
|
353
359
|
end
|
|
354
|
-
|
|
360
|
+
|
|
355
361
|
def method_missing(method_id, *args, &block)
|
|
356
362
|
if method_id == :not
|
|
357
363
|
return NotOperatorBuilder.new(@name)
|
|
358
364
|
end
|
|
359
365
|
end
|
|
360
|
-
|
|
366
|
+
|
|
361
367
|
def ==(value)
|
|
362
368
|
@atom_type = :equals
|
|
363
369
|
@value = value
|
|
364
|
-
create_block value, lambda {|x,y| x == y}, lambda {|x| x == value}; self
|
|
370
|
+
create_block value, lambda {|x,y| convert(x, y) == y}, lambda {|x| convert(x, value) == value}; self
|
|
365
371
|
end
|
|
366
|
-
|
|
372
|
+
|
|
367
373
|
def >(value)
|
|
368
|
-
create_block value, lambda {|x,y| x > y}, lambda {|x| x > value}; self
|
|
374
|
+
create_block value, lambda {|x,y| x != nil && convert(x, y) > y}, lambda {|x| x != nil && convert(x, value) > value}; self
|
|
369
375
|
end
|
|
370
|
-
|
|
376
|
+
|
|
371
377
|
def <(value)
|
|
372
|
-
create_block value, lambda {|x,y| x < y}, lambda {|x| x < value}; self
|
|
378
|
+
create_block value, lambda {|x,y| x != nil && convert(x, y) < y}, lambda {|x| x != nil && convert(x, value) < value}; self
|
|
373
379
|
end
|
|
374
|
-
|
|
380
|
+
|
|
375
381
|
def =~(value)
|
|
376
|
-
create_block value, lambda {|x,y| x =~ y}, lambda {|x| x =~ value}; self
|
|
382
|
+
create_block value, lambda {|x,y| convert(x, y) =~ y}, lambda {|x| convert(x, value) =~ value}; self
|
|
377
383
|
end
|
|
378
|
-
|
|
384
|
+
|
|
379
385
|
def <=(value)
|
|
380
|
-
create_block value, lambda {|x,y| x <= y}, lambda {|x| x <= value}; self
|
|
386
|
+
create_block value, lambda {|x,y| x != nil && convert(x, y) <= y}, lambda {|x| x != nil && convert(x, value) <= value}; self
|
|
381
387
|
end
|
|
382
|
-
|
|
388
|
+
|
|
383
389
|
def >=(value)
|
|
384
|
-
create_block value, lambda {|x,y| x >= y}, lambda {|x| x >= value}; self
|
|
390
|
+
create_block value, lambda {|x,y| x != nil && convert(x, y) >= y}, lambda {|x| x != nil && convert(x, value) >= value}; self
|
|
385
391
|
end
|
|
386
|
-
|
|
392
|
+
|
|
393
|
+
def convert(x, value)
|
|
394
|
+
if x.nil?
|
|
395
|
+
return x
|
|
396
|
+
end
|
|
397
|
+
return x.to_f if value.kind_of? Float
|
|
398
|
+
return x.to_i if value.kind_of? Numeric
|
|
399
|
+
return x
|
|
400
|
+
end
|
|
401
|
+
|
|
387
402
|
def build_atoms(tags,methods,when_id)
|
|
388
403
|
atoms = @child_atom_builders.map { |atom_builder|
|
|
389
404
|
tags[atom_builder.tag] = when_id
|
|
@@ -398,7 +413,7 @@ module Ruleby
|
|
|
398
413
|
return atoms << Core::PropertyAtom.new(@tag, @name, @deftemplate, &@block)
|
|
399
414
|
end
|
|
400
415
|
end
|
|
401
|
-
|
|
416
|
+
|
|
402
417
|
if references_self?(tags,when_id)
|
|
403
418
|
bind_methods = @bindings.collect{ |bb| methods[bb.tag] }
|
|
404
419
|
atoms << Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate,&@block)
|
|
@@ -407,7 +422,7 @@ module Ruleby
|
|
|
407
422
|
atoms << Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate,&@block)
|
|
408
423
|
end
|
|
409
424
|
end
|
|
410
|
-
|
|
425
|
+
|
|
411
426
|
private
|
|
412
427
|
def references_self?(tags,when_id)
|
|
413
428
|
ref_self = 0
|
|
@@ -416,14 +431,14 @@ module Ruleby
|
|
|
416
431
|
ref_self += 1
|
|
417
432
|
end
|
|
418
433
|
end
|
|
419
|
-
|
|
434
|
+
|
|
420
435
|
if ref_self > 0 and ref_self != @bindings.size
|
|
421
436
|
raise 'Binding to self and another pattern in the same condition is not yet supported.'
|
|
422
437
|
end
|
|
423
|
-
|
|
438
|
+
|
|
424
439
|
return ref_self > 0
|
|
425
440
|
end
|
|
426
|
-
|
|
441
|
+
|
|
427
442
|
def create_block(value, ref_block, basic_block)
|
|
428
443
|
if value && value.kind_of?(BindingBuilder)
|
|
429
444
|
@bindings = [value]
|
|
@@ -437,7 +452,7 @@ module Ruleby
|
|
|
437
452
|
end
|
|
438
453
|
end
|
|
439
454
|
end
|
|
440
|
-
|
|
455
|
+
|
|
441
456
|
class NotOperatorBuilder < AtomBuilder
|
|
442
457
|
def ==(value)
|
|
443
458
|
create_block value, lambda {|x,y| x != y}, lambda {|x| x != value}; self
|
data/lib/dsl/letigre.rb
CHANGED
metadata
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruleby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
4
|
+
prerelease: true
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 8
|
|
8
|
+
- b1
|
|
9
|
+
version: 0.8.b1
|
|
5
10
|
platform: ruby
|
|
6
11
|
authors:
|
|
7
|
-
- Joe Kutner
|
|
8
|
-
- Matt Smith
|
|
12
|
+
- Joe Kutner
|
|
13
|
+
- Matt Smith
|
|
9
14
|
autorequire:
|
|
10
15
|
bindir: bin
|
|
11
16
|
cert_chain: []
|
|
12
17
|
|
|
13
|
-
date:
|
|
18
|
+
date: 2011-03-29 00:00:00 -05:00
|
|
14
19
|
default_executable:
|
|
15
20
|
dependencies: []
|
|
16
21
|
|
|
@@ -28,17 +33,17 @@ extensions: []
|
|
|
28
33
|
extra_rdoc_files: []
|
|
29
34
|
|
|
30
35
|
files:
|
|
31
|
-
- lib/
|
|
32
|
-
- lib/
|
|
33
|
-
- lib/
|
|
34
|
-
- lib/core/
|
|
35
|
-
- lib/core/
|
|
36
|
-
- lib/
|
|
37
|
-
- lib/
|
|
38
|
-
- lib/
|
|
39
|
-
- lib/
|
|
40
|
-
- lib/
|
|
41
|
-
- lib/
|
|
36
|
+
- lib/rule_helper.rb
|
|
37
|
+
- lib/rulebook.rb
|
|
38
|
+
- lib/ruleby.rb
|
|
39
|
+
- lib/core/atoms.rb
|
|
40
|
+
- lib/core/engine.rb
|
|
41
|
+
- lib/core/nodes.rb
|
|
42
|
+
- lib/core/patterns.rb
|
|
43
|
+
- lib/core/utils.rb
|
|
44
|
+
- lib/dsl/ferrari.rb
|
|
45
|
+
- lib/dsl/letigre.rb
|
|
46
|
+
- lib/dsl/steel.rb
|
|
42
47
|
has_rdoc: true
|
|
43
48
|
homepage: http://ruleby.org
|
|
44
49
|
licenses: []
|
|
@@ -47,25 +52,31 @@ post_install_message:
|
|
|
47
52
|
rdoc_options: []
|
|
48
53
|
|
|
49
54
|
require_paths:
|
|
50
|
-
- lib
|
|
55
|
+
- lib
|
|
51
56
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
57
|
requirements:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
segments:
|
|
61
|
+
- 1
|
|
62
|
+
- 8
|
|
63
|
+
- 2
|
|
64
|
+
version: 1.8.2
|
|
57
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
66
|
requirements:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
- - ">"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
segments:
|
|
70
|
+
- 1
|
|
71
|
+
- 3
|
|
72
|
+
- 1
|
|
73
|
+
version: 1.3.1
|
|
63
74
|
requirements: []
|
|
64
75
|
|
|
65
76
|
rubyforge_project: ruleby
|
|
66
|
-
rubygems_version: 1.3.
|
|
77
|
+
rubygems_version: 1.3.6
|
|
67
78
|
signing_key:
|
|
68
79
|
specification_version: 3
|
|
69
80
|
summary: Rete based Ruby Rule Engine
|
|
70
81
|
test_files:
|
|
71
|
-
- tests/test.rb
|
|
82
|
+
- tests/test.rb
|