wongi-engine 0.0.1
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/.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
|