wongi-engine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +349 -0
- data/Rakefile +2 -0
- data/examples/ex01.rb +23 -0
- data/examples/ex02.rb +36 -0
- data/examples/graphviz.rb +15 -0
- data/examples/timeline.rb +48 -0
- data/lib/wongi-engine.rb +22 -0
- data/lib/wongi-engine/alpha_memory.rb +46 -0
- data/lib/wongi-engine/beta.rb +10 -0
- data/lib/wongi-engine/beta/beta_memory.rb +48 -0
- data/lib/wongi-engine/beta/beta_node.rb +164 -0
- data/lib/wongi-engine/beta/filter_node.rb +109 -0
- data/lib/wongi-engine/beta/join_node.rb +127 -0
- data/lib/wongi-engine/beta/ncc_node.rb +46 -0
- data/lib/wongi-engine/beta/ncc_partner.rb +43 -0
- data/lib/wongi-engine/beta/neg_node.rb +58 -0
- data/lib/wongi-engine/beta/optional_node.rb +43 -0
- data/lib/wongi-engine/beta/or_node.rb +76 -0
- data/lib/wongi-engine/beta/production_node.rb +31 -0
- data/lib/wongi-engine/core_ext.rb +57 -0
- data/lib/wongi-engine/dsl.rb +112 -0
- data/lib/wongi-engine/dsl/action.rb +12 -0
- data/lib/wongi-engine/dsl/actions/error_generator.rb +42 -0
- data/lib/wongi-engine/dsl/actions/simple_action.rb +23 -0
- data/lib/wongi-engine/dsl/actions/simple_collector.rb +51 -0
- data/lib/wongi-engine/dsl/actions/statement_generator.rb +52 -0
- data/lib/wongi-engine/dsl/actions/trace_action.rb +52 -0
- data/lib/wongi-engine/dsl/any_rule.rb +48 -0
- data/lib/wongi-engine/dsl/dsl_builder.rb +44 -0
- data/lib/wongi-engine/dsl/dsl_extensions.rb +43 -0
- data/lib/wongi-engine/dsl/extension_clause.rb +36 -0
- data/lib/wongi-engine/dsl/generation_clause.rb +15 -0
- data/lib/wongi-engine/dsl/generic_production_rule.rb +78 -0
- data/lib/wongi-engine/dsl/ncc_production_rule.rb +21 -0
- data/lib/wongi-engine/dsl/production_rule.rb +4 -0
- data/lib/wongi-engine/dsl/query.rb +24 -0
- data/lib/wongi-engine/graph.rb +71 -0
- data/lib/wongi-engine/model_context.rb +13 -0
- data/lib/wongi-engine/network.rb +416 -0
- data/lib/wongi-engine/network/collectable.rb +42 -0
- data/lib/wongi-engine/network/debug.rb +25 -0
- data/lib/wongi-engine/ruleset.rb +74 -0
- data/lib/wongi-engine/template.rb +111 -0
- data/lib/wongi-engine/token.rb +137 -0
- data/lib/wongi-engine/version.rb +5 -0
- data/lib/wongi-engine/wme.rb +134 -0
- data/lib/wongi-engine/wme_match_data.rb +34 -0
- data/spec/dataset_spec.rb +26 -0
- data/spec/dsl_spec.rb +9 -0
- data/spec/high_level_spec.rb +341 -0
- data/spec/ruleset_spec.rb +54 -0
- data/spec/simple_action_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/wme_spec.rb +83 -0
- data/wongi-engine.gemspec +19 -0
- metadata +110 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module NetworkParts
|
3
|
+
|
4
|
+
module Collectable
|
5
|
+
|
6
|
+
def collectors name = nil
|
7
|
+
@collectors ||= { }
|
8
|
+
if name
|
9
|
+
@collectors[name] ||= [ ]
|
10
|
+
else
|
11
|
+
@collectors
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_collectors
|
16
|
+
collectors :error
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_collector collector, name
|
20
|
+
collectors( name ) << collector
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_error_collector
|
24
|
+
add_collector collector, :error
|
25
|
+
end
|
26
|
+
|
27
|
+
def collection name
|
28
|
+
collectors( name ).map( &:default_collect ).flatten.uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
error_collectors.map( &:errors ).flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
def collected_tokens name
|
36
|
+
collectors( name ).map { |collector| collector.production.tokens }.flatten
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
module NetworkParts
|
4
|
+
|
5
|
+
module Debug
|
6
|
+
|
7
|
+
def full_wme_dump
|
8
|
+
@timeline.each_with_index do |slice, index|
|
9
|
+
puts "time #{ index - @timeline.length }"
|
10
|
+
slice.each do |key, alpha|
|
11
|
+
puts "\t#{alpha.template} -> [#{alpha.wmes.map(&:to_s).join ", "}]"
|
12
|
+
end
|
13
|
+
puts ""
|
14
|
+
end
|
15
|
+
puts "time 0"
|
16
|
+
alpha_hash.each do |key, alpha|
|
17
|
+
puts "\t#{alpha.template} -> [#{alpha.wmes.map(&:to_s).join ", "}]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Wongi
|
2
|
+
module Engine
|
3
|
+
class Ruleset
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def [] name
|
8
|
+
raise "undefined ruleset #{name}" unless rulesets.has_key?( name )
|
9
|
+
rulesets[ name ]
|
10
|
+
end
|
11
|
+
|
12
|
+
def register name, ruleset
|
13
|
+
raise "ruleset #{name} already exists" if rulesets.has_key?( name )
|
14
|
+
rulesets[ name ] = ruleset
|
15
|
+
end
|
16
|
+
|
17
|
+
def rulesets
|
18
|
+
@rulesets ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@rulesets = { }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize name = nil
|
28
|
+
@rules = []
|
29
|
+
self.name( name ) if name
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"<Ruleset #{name}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
def install rete
|
37
|
+
# puts "Installing ruleset #{name}"
|
38
|
+
@rules.each { |rule| rete << rule }
|
39
|
+
rescue Exception => e
|
40
|
+
e1 = Exception.new "error installing ruleset '#{name||'<unnamed>'}': #{e}"
|
41
|
+
e1.set_backtrace e.backtrace
|
42
|
+
raise e1
|
43
|
+
end
|
44
|
+
|
45
|
+
def name name = nil
|
46
|
+
if name && ! @name
|
47
|
+
self.class.register name, self
|
48
|
+
@name = name
|
49
|
+
end
|
50
|
+
@name
|
51
|
+
end
|
52
|
+
|
53
|
+
# def uri uri = nil
|
54
|
+
# @uri = uri if uri
|
55
|
+
# @uri
|
56
|
+
# end
|
57
|
+
|
58
|
+
def rule name, &definition
|
59
|
+
r = ProductionRule.new name
|
60
|
+
r.instance_eval &definition
|
61
|
+
@rules << r
|
62
|
+
r
|
63
|
+
end
|
64
|
+
|
65
|
+
def query name, &definition
|
66
|
+
r = Query.new name
|
67
|
+
r.instance_eval &definition
|
68
|
+
@rules << r
|
69
|
+
r
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class Template < Struct.new( :subject, :predicate, :object, :time )
|
4
|
+
|
5
|
+
include CoreExt
|
6
|
+
|
7
|
+
attr_predicate debug: false
|
8
|
+
|
9
|
+
def self.variable? thing
|
10
|
+
Symbol === thing && thing =~ /^[A-Z]/
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize s = :_, p = :_, o = :_, time = 0
|
14
|
+
raise "Cannot work with continuous time" unless time.integer?
|
15
|
+
raise "Cannot look into the future" if time > 0
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def import_into r
|
20
|
+
self.class.new r.import( subject ), r.import( predicate ), r.import( object ), time
|
21
|
+
end
|
22
|
+
|
23
|
+
def root?
|
24
|
+
subject == :_ && predicate == :_ && object == :_
|
25
|
+
end
|
26
|
+
|
27
|
+
def contains? var
|
28
|
+
self.class.variable?( var ) && array_form.include?( var )
|
29
|
+
end
|
30
|
+
|
31
|
+
def hash
|
32
|
+
@hash ||= array_form.map( &:hash ).hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.hash_for *args
|
36
|
+
args.map( &:hash ).hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def === wme
|
40
|
+
wme =~ self if WME === wme
|
41
|
+
end
|
42
|
+
|
43
|
+
def == other
|
44
|
+
return false unless Template === other
|
45
|
+
subject == other.subject && predicate == other.predicate && object == other.object
|
46
|
+
end
|
47
|
+
|
48
|
+
def =~ template
|
49
|
+
case template
|
50
|
+
when Template
|
51
|
+
( template.subject == :_ || template.subject == subject ) &&
|
52
|
+
( template.predicate == :_ || template.predicate == predicate ) &&
|
53
|
+
( template.object == :_ || template.object == object )
|
54
|
+
else
|
55
|
+
raise "Templates can only match templates"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def compile context
|
61
|
+
tests, assignment = *JoinNode.compile( self, context.earlier, context.parameters )
|
62
|
+
alpha = context.rete.compile_alpha( self )
|
63
|
+
context.node = context.node.beta_memory.join_node( alpha, tests, assignment, context.alpha_deaf )
|
64
|
+
context.earlier << self
|
65
|
+
context
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
"<~ #{subject.inspect} #{predicate.inspect} #{object.inspect} #{time}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
inspect
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def array_form
|
79
|
+
@array_form ||= [ subject, predicate, object ]
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class NegTemplate < Template
|
85
|
+
# :arg: context => Wongi::Rete::BetaNode::CompilationContext
|
86
|
+
def compile context
|
87
|
+
tests, _ = *JoinNode.compile( self, context.earlier, context.parameters )
|
88
|
+
alpha = context.rete.compile_alpha( self )
|
89
|
+
context.node = context.node.neg_node( alpha, tests, context.alpha_deaf )
|
90
|
+
context.node.debug = debug?
|
91
|
+
context.earlier << self
|
92
|
+
context
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class OptionalTemplate < Template
|
97
|
+
|
98
|
+
def compile context
|
99
|
+
tests, assignment = *JoinNode.compile( self, context.earlier, context.parameters )
|
100
|
+
alpha = context.rete.compile_alpha( self )
|
101
|
+
context.node = context.node.beta_memory.optional_node( alpha, tests, assignment, context.alpha_deaf )
|
102
|
+
context.node.debug = debug?
|
103
|
+
context.earlier << self
|
104
|
+
context
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class Token
|
4
|
+
|
5
|
+
include CoreExt
|
6
|
+
|
7
|
+
attr_reader :parent, :wme, :children
|
8
|
+
attr_accessor :node, :owner
|
9
|
+
attr_reader :neg_join_results
|
10
|
+
attr_reader :opt_join_results
|
11
|
+
attr_reader :ncc_results
|
12
|
+
attr_reader :generated_wmes
|
13
|
+
attr_predicate :has_optional
|
14
|
+
|
15
|
+
def initialize token, wme, assignments
|
16
|
+
@parent, @wme, @assignments = token, wme, assignments
|
17
|
+
@children = []
|
18
|
+
@neg_join_results = []
|
19
|
+
@opt_join_results = []
|
20
|
+
@ncc_results = []
|
21
|
+
@generated_wmes = []
|
22
|
+
@deexecutors = []
|
23
|
+
token.children << self if token
|
24
|
+
wme.tokens << self if wme
|
25
|
+
end
|
26
|
+
|
27
|
+
def subst variable, value
|
28
|
+
@cached_assignments = nil
|
29
|
+
if @assignments.has_key? variable
|
30
|
+
@assignments[ variable ] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def assignments
|
35
|
+
@cached_assignments ||= all_assignments
|
36
|
+
end
|
37
|
+
|
38
|
+
def [] var
|
39
|
+
assignments[ var ]
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
str = "TOKEN [\n"
|
44
|
+
all_assignments.each_pair { |key, value| str << "\t#{key} => #{value}\n" }
|
45
|
+
str << "]"
|
46
|
+
str
|
47
|
+
end
|
48
|
+
|
49
|
+
def wmes
|
50
|
+
if parent
|
51
|
+
parent.wmes + [wme]
|
52
|
+
else
|
53
|
+
[wme]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete preserve_self = false
|
58
|
+
delete_children
|
59
|
+
# => TODO: why was this last check needed? consult the Rete PhD
|
60
|
+
@node.tokens.delete self unless preserve_self# or @node.kind_of?( NccPartner )
|
61
|
+
@wme.tokens.delete self if @wme
|
62
|
+
@parent.children.delete self if @parent
|
63
|
+
|
64
|
+
retract_generated
|
65
|
+
deexecute
|
66
|
+
|
67
|
+
case @node
|
68
|
+
when NegNode
|
69
|
+
@neg_join_results.each do |njr|
|
70
|
+
njr.wme.neg_join_results.delete njr if njr.wme
|
71
|
+
end
|
72
|
+
@neg_join_results = []
|
73
|
+
|
74
|
+
when OptionalNode
|
75
|
+
@opt_join_results.each do |ojr|
|
76
|
+
ojr.wme.opt_join_results.delete ojr
|
77
|
+
end
|
78
|
+
@opt_join_results = []
|
79
|
+
|
80
|
+
when NccNode
|
81
|
+
@ncc_results.each do |nccr|
|
82
|
+
nccr.wme.tokens.delete nccr
|
83
|
+
nccr.parent.children.delete nccr
|
84
|
+
end
|
85
|
+
@ncc_results = []
|
86
|
+
|
87
|
+
when NccPartner
|
88
|
+
@owner.ncc_results.delete self
|
89
|
+
if @owner.ncc_results.empty?
|
90
|
+
@node.ncc.children.each do |node|
|
91
|
+
node.left_activate @owner, nil, {}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def delete_children
|
99
|
+
while @children.first
|
100
|
+
@children.first.delete
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
|
107
|
+
def retract_generated
|
108
|
+
|
109
|
+
@generated_wmes.each do |wme|
|
110
|
+
unless wme.manual? # => TODO: does this ever fail at all?
|
111
|
+
wme.generating_tokens.delete self
|
112
|
+
if wme.generating_tokens.empty?
|
113
|
+
wme.rete.retract wme, true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
@generated_wmes = []
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
def deexecute
|
122
|
+
@deexecutors.each { |deexec| deexec.deexecute self }
|
123
|
+
@deexecutors = []
|
124
|
+
end
|
125
|
+
|
126
|
+
def all_assignments
|
127
|
+
raise "Assignments is not a hash" unless @assignments.kind_of?( Hash )
|
128
|
+
if @parent
|
129
|
+
@parent.assignments.merge @assignments
|
130
|
+
else
|
131
|
+
@assignments
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class WME < Struct.new( :subject, :predicate, :object )
|
4
|
+
|
5
|
+
attr_reader :rete
|
6
|
+
|
7
|
+
attr_reader :alphas, :tokens, :generating_tokens
|
8
|
+
attr_reader :neg_join_results, :opt_join_results
|
9
|
+
|
10
|
+
def initialize s, p, o, r = nil
|
11
|
+
|
12
|
+
@alphas = []
|
13
|
+
@tokens = []
|
14
|
+
@generating_tokens = []
|
15
|
+
@neg_join_results = []
|
16
|
+
@opt_join_results = []
|
17
|
+
|
18
|
+
@rete = r
|
19
|
+
|
20
|
+
if r
|
21
|
+
super( r.import(s), r.import(p), r.import(o) )
|
22
|
+
else
|
23
|
+
super( s, p, o )
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def import_into r
|
29
|
+
self.class.new subject, predicate, object, r
|
30
|
+
end
|
31
|
+
|
32
|
+
def dup
|
33
|
+
self.class.new subject, predicate, object, rete
|
34
|
+
end
|
35
|
+
|
36
|
+
def == other
|
37
|
+
subject == other.subject && predicate == other.predicate && object == other.object
|
38
|
+
end
|
39
|
+
|
40
|
+
def =~ template
|
41
|
+
raise "Cannot match a WME against a #{template.class}" unless Template === template
|
42
|
+
result = match_member( template, :subject ) & match_member( template, :predicate ) & match_member( template, :object )
|
43
|
+
if result.match?
|
44
|
+
result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def manual?
|
49
|
+
generating_tokens.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def generated?
|
53
|
+
!manual?
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy
|
57
|
+
|
58
|
+
alphas.each { |alpha| alpha.remove self }.clear
|
59
|
+
while tokens.first
|
60
|
+
tokens.first.delete self # => will remove itself from the array
|
61
|
+
end
|
62
|
+
|
63
|
+
destroy_neg_join_results
|
64
|
+
destroy_opt_join_results
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
"<WME #{subject.inspect} #{predicate.inspect} #{object.inspect}>"
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
inspect
|
74
|
+
end
|
75
|
+
|
76
|
+
def hash
|
77
|
+
@hash ||= array_form.map( &:hash ).hash
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def array_form
|
83
|
+
@array_form ||= [ subject, predicate, object ]
|
84
|
+
end
|
85
|
+
|
86
|
+
def destroy_neg_join_results
|
87
|
+
neg_join_results.each do |njr|
|
88
|
+
|
89
|
+
token = njr.owner
|
90
|
+
results = token.neg_join_results
|
91
|
+
results.delete njr
|
92
|
+
|
93
|
+
if results.empty? && !rete.in_snapshot?
|
94
|
+
token.node.children.each { |beta|
|
95
|
+
beta.left_activate token, nil, { }
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
end.clear
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy_opt_join_results
|
103
|
+
opt_join_results.each do |ojr|
|
104
|
+
|
105
|
+
token = ojr.owner
|
106
|
+
results = token.opt_join_results
|
107
|
+
results.delete ojr
|
108
|
+
|
109
|
+
if results.empty?
|
110
|
+
token.delete_children
|
111
|
+
token.node.children.each { |beta|
|
112
|
+
beta.left_activate token
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
end.clear
|
117
|
+
end
|
118
|
+
|
119
|
+
def match_member template, member
|
120
|
+
result = WMEMatchData.new
|
121
|
+
mine = self.send member
|
122
|
+
theirs = template.send member
|
123
|
+
if theirs == :_ || mine == theirs
|
124
|
+
result.match!
|
125
|
+
elsif Template.variable? theirs
|
126
|
+
result.match!
|
127
|
+
result[theirs] = mine
|
128
|
+
end
|
129
|
+
result
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|