mort666-wongi-engine 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.hgignore +6 -0
  4. data/.hgtags +13 -0
  5. data/.ruby-gemset +1 -0
  6. data/.travis.yml +19 -0
  7. data/CHANGELOG.md +106 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +22 -0
  10. data/README.md +27 -0
  11. data/Rakefile +9 -0
  12. data/examples/ex01.rb +23 -0
  13. data/examples/ex02.rb +37 -0
  14. data/examples/graphviz.rb +16 -0
  15. data/examples/rdf.n3 +6 -0
  16. data/examples/rdf.rb +14 -0
  17. data/examples/timeline.rb +48 -0
  18. data/lib/wongi-engine.rb +36 -0
  19. data/lib/wongi-engine/alpha_memory.rb +60 -0
  20. data/lib/wongi-engine/beta.rb +11 -0
  21. data/lib/wongi-engine/beta/assignment_node.rb +40 -0
  22. data/lib/wongi-engine/beta/beta_memory.rb +49 -0
  23. data/lib/wongi-engine/beta/beta_node.rb +94 -0
  24. data/lib/wongi-engine/beta/filter_node.rb +48 -0
  25. data/lib/wongi-engine/beta/join_node.rb +140 -0
  26. data/lib/wongi-engine/beta/ncc_node.rb +67 -0
  27. data/lib/wongi-engine/beta/ncc_partner.rb +40 -0
  28. data/lib/wongi-engine/beta/neg_node.rb +115 -0
  29. data/lib/wongi-engine/beta/optional_node.rb +142 -0
  30. data/lib/wongi-engine/beta/or_node.rb +37 -0
  31. data/lib/wongi-engine/beta/production_node.rb +31 -0
  32. data/lib/wongi-engine/compiler.rb +115 -0
  33. data/lib/wongi-engine/core_ext.rb +63 -0
  34. data/lib/wongi-engine/data_overlay.rb +144 -0
  35. data/lib/wongi-engine/dsl.rb +132 -0
  36. data/lib/wongi-engine/dsl/action/base.rb +11 -0
  37. data/lib/wongi-engine/dsl/action/error_generator.rb +31 -0
  38. data/lib/wongi-engine/dsl/action/simple_action.rb +60 -0
  39. data/lib/wongi-engine/dsl/action/simple_collector.rb +52 -0
  40. data/lib/wongi-engine/dsl/action/statement_generator.rb +46 -0
  41. data/lib/wongi-engine/dsl/action/trace_action.rb +49 -0
  42. data/lib/wongi-engine/dsl/any_rule.rb +33 -0
  43. data/lib/wongi-engine/dsl/assuming.rb +31 -0
  44. data/lib/wongi-engine/dsl/builder.rb +44 -0
  45. data/lib/wongi-engine/dsl/clause/assign.rb +15 -0
  46. data/lib/wongi-engine/dsl/clause/fact.rb +71 -0
  47. data/lib/wongi-engine/dsl/clause/gen.rb +17 -0
  48. data/lib/wongi-engine/dsl/clause/generic.rb +38 -0
  49. data/lib/wongi-engine/dsl/generated.rb +43 -0
  50. data/lib/wongi-engine/dsl/ncc_subrule.rb +17 -0
  51. data/lib/wongi-engine/dsl/query.rb +24 -0
  52. data/lib/wongi-engine/dsl/rule.rb +84 -0
  53. data/lib/wongi-engine/enumerators.rb +21 -0
  54. data/lib/wongi-engine/error.rb +22 -0
  55. data/lib/wongi-engine/filter.rb +6 -0
  56. data/lib/wongi-engine/filter/asserting_test.rb +20 -0
  57. data/lib/wongi-engine/filter/equality_test.rb +36 -0
  58. data/lib/wongi-engine/filter/filter_test.rb +18 -0
  59. data/lib/wongi-engine/filter/greater_than_test.rb +36 -0
  60. data/lib/wongi-engine/filter/inequality_test.rb +36 -0
  61. data/lib/wongi-engine/filter/less_than_test.rb +36 -0
  62. data/lib/wongi-engine/graph.rb +73 -0
  63. data/lib/wongi-engine/network.rb +416 -0
  64. data/lib/wongi-engine/network/collectable.rb +42 -0
  65. data/lib/wongi-engine/network/debug.rb +85 -0
  66. data/lib/wongi-engine/ruleset.rb +74 -0
  67. data/lib/wongi-engine/template.rb +78 -0
  68. data/lib/wongi-engine/token.rb +114 -0
  69. data/lib/wongi-engine/version.rb +5 -0
  70. data/lib/wongi-engine/wme.rb +89 -0
  71. data/lib/wongi-engine/wme_match_data.rb +34 -0
  72. data/spec/beta_node_spec.rb +29 -0
  73. data/spec/bug_specs/issue_4_spec.rb +141 -0
  74. data/spec/dataset_spec.rb +27 -0
  75. data/spec/dsl_spec.rb +9 -0
  76. data/spec/filter_specs/assert_test_spec.rb +102 -0
  77. data/spec/filter_specs/less_test_spec.rb +41 -0
  78. data/spec/generation_spec.rb +116 -0
  79. data/spec/high_level_spec.rb +378 -0
  80. data/spec/network_spec.rb +182 -0
  81. data/spec/overlay_spec.rb +61 -0
  82. data/spec/rule_specs/any_rule_spec.rb +75 -0
  83. data/spec/rule_specs/assign_spec.rb +88 -0
  84. data/spec/rule_specs/assuming_spec.rb +66 -0
  85. data/spec/rule_specs/maybe_rule_spec.rb +101 -0
  86. data/spec/rule_specs/ncc_spec.rb +258 -0
  87. data/spec/rule_specs/negative_rule_spec.rb +105 -0
  88. data/spec/ruleset_spec.rb +54 -0
  89. data/spec/simple_action_spec.rb +40 -0
  90. data/spec/spec_helper.rb +3 -0
  91. data/spec/wme_spec.rb +83 -0
  92. data/wongi-engine.gemspec +40 -0
  93. metadata +212 -0
@@ -0,0 +1,21 @@
1
+ module Wongi::Engine
2
+ class DuplicatingEnumerator
3
+ def self.new(collection)
4
+ Enumerator.new do |y|
5
+ collection.dup.each do |e|
6
+ y << e
7
+ end
8
+ end
9
+ end
10
+ end
11
+ class DeleteSafeEnumerator
12
+ def self.new(collection)
13
+ Enumerator.new do |y|
14
+ collection.dup.each do |e|
15
+ y << e unless e.deleted?
16
+ end
17
+ collection.reject! &:deleted?
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module Wongi::Engine
2
+
3
+ class Error < StandardError
4
+
5
+ end
6
+
7
+ class DefinitionError < Error
8
+
9
+ end
10
+
11
+ class ReteError
12
+ attr_reader :token, :message
13
+ def initialize token, message, literate
14
+ @token, @message, @literate = token, message, literate
15
+ end
16
+ def literate?
17
+ @literate
18
+ end
19
+ end
20
+
21
+
22
+ end
@@ -0,0 +1,6 @@
1
+ require 'wongi-engine/filter/filter_test'
2
+ require 'wongi-engine/filter/equality_test'
3
+ require 'wongi-engine/filter/inequality_test'
4
+ require 'wongi-engine/filter/asserting_test'
5
+ require 'wongi-engine/filter/less_than_test'
6
+ require 'wongi-engine/filter/greater_than_test'
@@ -0,0 +1,20 @@
1
+ module Wongi::Engine
2
+
3
+ class AssertingTest < FilterTest
4
+
5
+ def initialize *vars, &body
6
+ @vars = vars
7
+ @body = body
8
+ end
9
+
10
+ def passes? token
11
+ if @vars.empty?
12
+ @body.call token
13
+ else
14
+ @body.call *( @vars.map { |var| token[var] } )
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,36 @@
1
+ module Wongi::Engine
2
+
3
+ class EqualityTest < FilterTest
4
+
5
+ attr_reader :x, :y
6
+
7
+ def initialize x, y
8
+ @x, @y = x, y
9
+ end
10
+
11
+ def passes? token
12
+
13
+ x = if Template.variable? @x
14
+ token[@x]
15
+ else
16
+ @x
17
+ end
18
+
19
+ y = if Template.variable? @y
20
+ token[@y]
21
+ else
22
+ @y
23
+ end
24
+
25
+ return false if x == :_ || y == :_
26
+ return x == y
27
+
28
+ end
29
+
30
+ def == other
31
+ super && x == other.x && y == other.y
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,18 @@
1
+ module Wongi::Engine
2
+
3
+ class FilterTest
4
+
5
+ def passes? token
6
+ raise "#{self.class} must implement #passes?"
7
+ end
8
+
9
+ def compile context
10
+ context.tap { |c| c.filter_node(self) }
11
+ end
12
+
13
+ def == other
14
+ false
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,36 @@
1
+ module Wongi::Engine
2
+
3
+ class GreaterThanTest < FilterTest
4
+
5
+ attr_reader :x, :y
6
+
7
+ def initialize x, y
8
+ @x, @y = x, y
9
+ end
10
+
11
+ def passes? token
12
+
13
+ x = if Template.variable? @x
14
+ token[@x]
15
+ else
16
+ @x
17
+ end
18
+
19
+ y = if Template.variable? @y
20
+ token[@y]
21
+ else
22
+ @y
23
+ end
24
+
25
+ return false if x == :_ || y == :_
26
+ return x > y
27
+
28
+ end
29
+
30
+ def == other
31
+ super && x == other.x && y == other.y
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,36 @@
1
+ module Wongi::Engine
2
+
3
+ class InequalityTest < FilterTest
4
+
5
+ attr_reader :x, :y
6
+
7
+ def initialize x, y
8
+ @x, @y = x, y
9
+ end
10
+
11
+ def passes? token
12
+
13
+ x = if Template.variable? @x
14
+ token[@x]
15
+ else
16
+ @x
17
+ end
18
+
19
+ y = if Template.variable? @y
20
+ token[@y]
21
+ else
22
+ @y
23
+ end
24
+
25
+ return false if x == :_ || y == :_
26
+ return x != y
27
+
28
+ end
29
+
30
+ def == other
31
+ super && x == other.x && y == other.y
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,36 @@
1
+ module Wongi::Engine
2
+
3
+ class LessThanTest < FilterTest
4
+
5
+ attr_reader :x, :y
6
+
7
+ def initialize x, y
8
+ @x, @y = x, y
9
+ end
10
+
11
+ def passes? token
12
+
13
+ x = if Template.variable? @x
14
+ token[@x]
15
+ else
16
+ @x
17
+ end
18
+
19
+ y = if Template.variable? @y
20
+ token[@y]
21
+ else
22
+ @y
23
+ end
24
+
25
+ return false if x == :_ || y == :_
26
+ return x < y
27
+
28
+ end
29
+
30
+ def == other
31
+ super && x == other.x && y == other.y
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,73 @@
1
+ module Wongi::Engine
2
+
3
+ class Graph
4
+
5
+ def initialize rete
6
+ @rete = rete
7
+ end
8
+
9
+ def dot io, opts = { }
10
+
11
+ @seen_betas = []
12
+
13
+ if String === io
14
+ File.open io, "w" do |actual_io|
15
+ dot actual_io
16
+ end
17
+ return
18
+ end
19
+
20
+ @io = io
21
+
22
+ @io.puts "digraph {"
23
+
24
+ dump_alphas(opts) unless opts[:alpha] == false
25
+ dump_betas(opts)
26
+
27
+ @io.puts "}"
28
+
29
+ ensure
30
+ @io = nil
31
+ end
32
+
33
+ private
34
+
35
+ def print_hash h
36
+ h.to_s.gsub /-/, '_'
37
+ end
38
+
39
+ def dump_alphas opts
40
+ @io.puts "subgraph cluster_alphas {"
41
+ @rete.alphas.select { |alpha| not alpha.betas.empty? }.each do |alpha|
42
+ @io.puts "node#{print_hash alpha.object_id} [shape=box label=\"#{alpha.template.to_s.gsub /"/, "\\\""}\"];"
43
+ end
44
+ @io.puts "};"
45
+ end
46
+
47
+ def dump_betas opts
48
+ dump_beta @rete.beta_top, opts
49
+ end
50
+
51
+ def dump_beta beta, opts
52
+ return if @seen_betas.include? beta
53
+ @seen_betas << beta
54
+ @io.puts "node#{print_hash beta.object_id} [label=\"#{beta.class.name.split('::').last}\"];"
55
+ if beta.is_a? NccNode
56
+ @io.puts "node#{print_hash beta.partner.object_id} -> node#{print_hash beta.object_id};"
57
+ @io.puts "{ rank=same; node#{print_hash beta.partner.object_id} node#{print_hash beta.object_id} }"
58
+ end
59
+ if beta.respond_to? :alpha and opts[:alpha] != false
60
+ alpha = beta.alpha
61
+ if alpha
62
+ @io.puts "node#{print_hash alpha.object_id} -> node#{print_hash beta.object_id};"
63
+ end
64
+ end
65
+ beta.children.each do |child|
66
+ @io.puts "node#{print_hash beta.object_id} -> node#{print_hash child.object_id};"
67
+ dump_beta child, opts
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,416 @@
1
+ require 'wongi-engine/network/collectable'
2
+ require 'wongi-engine/network/debug'
3
+
4
+ module Wongi::Engine
5
+ class Network
6
+
7
+ attr_reader :alpha_top, :beta_top
8
+ attr_reader :queries, :results
9
+ attr_reader :productions
10
+ attr_reader :overlays
11
+
12
+ include NetworkParts::Collectable
13
+
14
+ protected
15
+ attr_accessor :alpha_hash
16
+ attr_writer :alpha_top, :beta_top
17
+ attr_writer :queries, :results
18
+
19
+ public
20
+
21
+ def debug!
22
+ extend NetworkParts::Debug
23
+ end
24
+
25
+ def rdf!
26
+ if ! defined? Wongi::RDF::DocumentSupport
27
+ begin
28
+ require 'wongi-rdf'
29
+ rescue LoadError => e
30
+ raise "'wongi-rdf' is required for RDF support"
31
+ end
32
+ end
33
+
34
+ extend Wongi::RDF::DocumentSupport
35
+ class << self
36
+ def statements
37
+ alpha_top.wmes
38
+ end
39
+ end
40
+
41
+ @namespaces = { }
42
+ @blank_counter = 1
43
+ @ns_counter = 0
44
+ @used_blanks = { }
45
+ end
46
+
47
+ def initialize
48
+ @timeline = []
49
+ self.alpha_top = AlphaMemory.new( Template.new( :_, :_, :_ ), self )
50
+ self.alpha_hash = { alpha_top.template.hash => alpha_top }
51
+ self.beta_top = BetaMemory.new(nil)
52
+ self.beta_top.rete = self
53
+ self.beta_top.seed
54
+ self.queries = {}
55
+ self.results = {}
56
+ @revns = {}
57
+
58
+ @productions = { }
59
+
60
+ @collectors = {}
61
+ @collectors[:error] = []
62
+
63
+ end
64
+
65
+ def dump
66
+ beta_top.dump
67
+ end
68
+
69
+ def with_overlay(&block)
70
+ default_overlay.with_child(&block)
71
+ end
72
+
73
+ def alphas
74
+ alpha_hash.values
75
+ end
76
+
77
+ # def import thing
78
+ # case thing
79
+ # when String, Numeric, TrueClass, FalseClass, NilClass, Wongi::RDF::Node
80
+ # thing
81
+ # when Symbol
82
+ # thing
83
+ # else
84
+ # thing
85
+ # end
86
+ # end
87
+
88
+ def default_overlay
89
+ @default_overlay ||= DataOverlay.new(self)
90
+ end
91
+
92
+ # @private
93
+ def add_overlay(o)
94
+ overlays << o
95
+ end
96
+
97
+ # @private
98
+ def remove_overlay(o)
99
+ overlays.delete(o) unless o == default_overlay
100
+ end
101
+
102
+ # @private
103
+ def current_overlay
104
+ overlays.last
105
+ end
106
+
107
+ # @private
108
+ def overlays
109
+ @overlays ||= []
110
+ end
111
+
112
+ def assert(wme)
113
+ default_overlay.assert(wme)
114
+ end
115
+
116
+ def retract(wme, options = {})
117
+ default_overlay.retract(wme, options)
118
+ end
119
+
120
+ # @private
121
+ def real_assert( wme )
122
+ unless wme.rete == self
123
+ wme = wme.import_into self
124
+ end
125
+
126
+ source = best_alpha(wme)
127
+ if existing = find(wme.subject, wme.predicate, wme.object)
128
+ existing.manual! if wme.manual?
129
+ return
130
+ end
131
+
132
+ alphas_for( wme ).each { |a| a.activate wme }
133
+
134
+ wme
135
+ end
136
+
137
+ # @private
138
+ def real_retract wme, options
139
+ real = find(wme.subject, wme.predicate, wme.object)
140
+ return if real.nil?
141
+ if real.generated? # still some generator tokens left
142
+ if real.manual?
143
+ real.manual = false
144
+ else
145
+ raise Error, "cannot retract automatic facts"
146
+ end
147
+ else
148
+ if options[:automatic] && real.manual? # auto-retracting a fact that has been added manually
149
+ return
150
+ end
151
+ end
152
+
153
+ alphas_for( real ).each { |a| a.deactivate real }
154
+ end
155
+
156
+ def wmes
157
+ alpha_top.wmes
158
+ end
159
+ alias_method :statements, :wmes
160
+ alias_method :facts, :wmes
161
+
162
+ def in_snapshot?
163
+ @in_snapshot
164
+ end
165
+
166
+ def snapshot!
167
+ @timeline.each_with_index do |slice, index|
168
+ source = if index == @timeline.size - 1
169
+ alpha_hash
170
+ else
171
+ @timeline[index+1]
172
+ end
173
+ # puts "source = #{source}"
174
+ wmes = {}
175
+ slice.each { |key, alpha| wmes[key] = alpha.wmes }
176
+ slice.each do |key, alpha|
177
+ in_snapshot {
178
+ wmes[key].dup.each { |wme| wme.destroy }
179
+ }
180
+ alpha.snapshot! source[key]
181
+ end
182
+ end
183
+ end
184
+
185
+ def rule name = nil, &block
186
+ r = DSL::Rule.new( name || generate_rule_name )
187
+ r.instance_eval &block
188
+ self << r
189
+ end
190
+
191
+ def query name, &block
192
+ q = DSL::Query.new name
193
+ q.instance_eval &block
194
+ self << q
195
+ end
196
+
197
+ def << something
198
+ if something.respond_to?( :install )
199
+ something.install( self )
200
+ else
201
+ case something
202
+ when Array
203
+ assert WME.new( *something ).tap { |wme| wme.overlay = default_overlay }
204
+ when WME
205
+ assert something
206
+ # when Wongi::RDF::Statement
207
+ # assert WME.new( something.subject, something.predicate, something.object, self )
208
+ #when Wongi::RDF::Document
209
+ # something.statements.each do |st|
210
+ # assert WME.new( st.subject, st.predicate, st.object, self )
211
+ # end
212
+ when Network
213
+ something.wmes.each { |wme| assert( wme ) }
214
+ else
215
+ raise Error, "I don't know how to accept a #{something.class}"
216
+ end
217
+ end
218
+ end
219
+
220
+ def install_rule( rule )
221
+ derived = rule.import_into self
222
+ production = build_production beta_top, derived.conditions, [], derived.actions, false
223
+ if rule.name
224
+ productions[ rule.name ] = production
225
+ end
226
+ production
227
+ end
228
+
229
+ def install_query( query )
230
+ derived = query.import_into self
231
+ prepare_query derived.name, derived.conditions, derived.parameters, derived.actions
232
+ end
233
+
234
+ def compile_alpha condition
235
+ template = Template.new :_, :_, :_
236
+ time = condition.time
237
+
238
+ template.subject = condition.subject unless Template.variable?( condition.subject )
239
+ template.predicate = condition.predicate unless Template.variable?( condition.predicate )
240
+ template.object = condition.object unless Template.variable?( condition.object )
241
+
242
+ hash = template.hash
243
+ # puts "COMPILED CONDITION #{condition} WITH KEY #{key}"
244
+ if time == 0
245
+ return self.alpha_hash[ hash ] if self.alpha_hash.has_key?( hash )
246
+ else
247
+ return @timeline[time+1][ hash ] if @timeline[time+1] && @timeline[time+1].has_key?( hash )
248
+ end
249
+
250
+ alpha = AlphaMemory.new( template, self )
251
+
252
+ if time == 0
253
+ self.alpha_hash[ hash ] = alpha
254
+ initial_fill alpha
255
+ else
256
+ if @timeline[time+1].nil?
257
+ # => ensure lineage from 0 to time
258
+ compile_alpha condition.class.new(condition.subject, condition.predicate, condition.object, time: time + 1)
259
+ @timeline.unshift Hash.new
260
+ end
261
+ @timeline[time+1][ hash ] = alpha
262
+ end
263
+ alpha
264
+ end
265
+
266
+ def cache s, p, o
267
+ compile_alpha Template.new(s, p, o)
268
+ end
269
+
270
+ # TODO: pick an alpha with fewer candidates to go through
271
+ def initial_fill alpha
272
+ alpha_top.wmes.each do |wme|
273
+ alpha.activate wme if wme =~ alpha.template
274
+ end
275
+ end
276
+
277
+ def remove_production pnode
278
+ delete_node_with_ancestors pnode
279
+ end
280
+
281
+ def prepare_query name, conditions, parameters, actions = []
282
+ query = self.queries[ name ] = BetaMemory.new( nil )
283
+ query.rete = self
284
+ query.seed(Hash[parameters.map{ |param| [param, nil]}])
285
+ self.results[ name ] = build_production query, conditions, parameters, actions, true
286
+ end
287
+
288
+ def execute name, valuations
289
+ beta = self.queries[name]
290
+ raise Error, "Undefined query #{name}; known queries are #{queries.keys}" unless beta
291
+ beta.subst valuations
292
+ end
293
+
294
+ def inspect
295
+ "<Rete>"
296
+ end
297
+
298
+ def exists? wme
299
+ find(wme.subject, wme.predicate, wme.object)
300
+ end
301
+
302
+ def each *args, &block
303
+ template = case args.length
304
+ when 0
305
+ Template.new(:_, :_, :_)
306
+ when 3
307
+ Template.new(*args)
308
+ else
309
+ raise Error, "Network#each expect a template or nothing at all"
310
+ end
311
+ source = best_alpha(template)
312
+ matching = current_overlay.wmes(source).select { |wme| wme =~ template }
313
+ if block_given?
314
+ matching.each(&block)
315
+ else
316
+ matching.each
317
+ end
318
+ end
319
+
320
+ def select s, p, o
321
+ template = Template.new(s, p, o)
322
+ source = best_alpha(template)
323
+ matching = current_overlay.wmes(source).select { |wme| wme =~ template }
324
+ if block_given?
325
+ matching.each { |st| yield st.subject, st.predicate, st.object }
326
+ end
327
+ matching
328
+ end
329
+
330
+ def find s, p, o
331
+ template = Template.new(s, p, o)
332
+ source = best_alpha(template)
333
+ # puts "looking for #{template} among #{source.wmes.size} triples of #{source.template}"
334
+ current_overlay.wmes(source).find { |wme| wme =~ template }
335
+ end
336
+
337
+ protected
338
+
339
+
340
+ def in_snapshot
341
+ @in_snapshot = true
342
+ yield
343
+ ensure
344
+ @in_snapshot = false
345
+ end
346
+
347
+ def generate_rule_name
348
+ "rule_#{productions.length}"
349
+ end
350
+
351
+ def alphas_for wme
352
+ s, p, o = wme.subject, wme.predicate, wme.object
353
+ [
354
+ lookup( s, p, o),
355
+ lookup( s, p, :_),
356
+ lookup( s, :_, o),
357
+ lookup(:_, p, o),
358
+ lookup( s, :_, :_),
359
+ lookup(:_, p, :_),
360
+ lookup(:_, :_, o),
361
+ lookup(:_, :_, :_),
362
+ ].compact!.tap(&:uniq!)
363
+ end
364
+
365
+ def lookup s, p, o
366
+ key = Template.hash_for(s, p, o)
367
+ # puts "Lookup for #{key}"
368
+ self.alpha_hash[ key ]
369
+ end
370
+
371
+ def alpha_activate alpha, wme
372
+ alpha.activate(wme)
373
+ end
374
+
375
+ def best_alpha template
376
+ alpha_hash.inject(nil) do |best, (_, alpha)|
377
+ if template =~ alpha.template && (best.nil? || alpha.size < best.size)
378
+ alpha
379
+ else
380
+ best
381
+ end
382
+ end
383
+ end
384
+
385
+ def build_production root, conditions, parameters, actions, alpha_deaf
386
+ compiler = Compiler.new(self, root, conditions, parameters, alpha_deaf)
387
+ ProductionNode.new(compiler.compile, actions).tap do |production|
388
+ production.compilation_context = compiler
389
+ production.refresh
390
+ end
391
+ end
392
+
393
+ def delete_node_with_ancestors node
394
+
395
+ if node.kind_of?( NccNode )
396
+ delete_node_with_ancestors node.partner
397
+ end
398
+
399
+ if [BetaMemory, NegNode, NccNode, NccPartner].any? { | klass| node.kind_of? klass }
400
+ while node.tokens.first
401
+ node.tokens.first.delete
402
+ end
403
+ end
404
+
405
+ if node.parent
406
+ node.parent.children.delete node
407
+ if node.parent.children.empty?
408
+ delete_node_with_ancestors(node.parent)
409
+ end
410
+ end
411
+
412
+ end
413
+
414
+ end
415
+
416
+ end