mort666-wongi-engine 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.hgignore +6 -0
- data/.hgtags +13 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +106 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +27 -0
- data/Rakefile +9 -0
- data/examples/ex01.rb +23 -0
- data/examples/ex02.rb +37 -0
- data/examples/graphviz.rb +16 -0
- data/examples/rdf.n3 +6 -0
- data/examples/rdf.rb +14 -0
- data/examples/timeline.rb +48 -0
- data/lib/wongi-engine.rb +36 -0
- data/lib/wongi-engine/alpha_memory.rb +60 -0
- data/lib/wongi-engine/beta.rb +11 -0
- data/lib/wongi-engine/beta/assignment_node.rb +40 -0
- data/lib/wongi-engine/beta/beta_memory.rb +49 -0
- data/lib/wongi-engine/beta/beta_node.rb +94 -0
- data/lib/wongi-engine/beta/filter_node.rb +48 -0
- data/lib/wongi-engine/beta/join_node.rb +140 -0
- data/lib/wongi-engine/beta/ncc_node.rb +67 -0
- data/lib/wongi-engine/beta/ncc_partner.rb +40 -0
- data/lib/wongi-engine/beta/neg_node.rb +115 -0
- data/lib/wongi-engine/beta/optional_node.rb +142 -0
- data/lib/wongi-engine/beta/or_node.rb +37 -0
- data/lib/wongi-engine/beta/production_node.rb +31 -0
- data/lib/wongi-engine/compiler.rb +115 -0
- data/lib/wongi-engine/core_ext.rb +63 -0
- data/lib/wongi-engine/data_overlay.rb +144 -0
- data/lib/wongi-engine/dsl.rb +132 -0
- data/lib/wongi-engine/dsl/action/base.rb +11 -0
- data/lib/wongi-engine/dsl/action/error_generator.rb +31 -0
- data/lib/wongi-engine/dsl/action/simple_action.rb +60 -0
- data/lib/wongi-engine/dsl/action/simple_collector.rb +52 -0
- data/lib/wongi-engine/dsl/action/statement_generator.rb +46 -0
- data/lib/wongi-engine/dsl/action/trace_action.rb +49 -0
- data/lib/wongi-engine/dsl/any_rule.rb +33 -0
- data/lib/wongi-engine/dsl/assuming.rb +31 -0
- data/lib/wongi-engine/dsl/builder.rb +44 -0
- data/lib/wongi-engine/dsl/clause/assign.rb +15 -0
- data/lib/wongi-engine/dsl/clause/fact.rb +71 -0
- data/lib/wongi-engine/dsl/clause/gen.rb +17 -0
- data/lib/wongi-engine/dsl/clause/generic.rb +38 -0
- data/lib/wongi-engine/dsl/generated.rb +43 -0
- data/lib/wongi-engine/dsl/ncc_subrule.rb +17 -0
- data/lib/wongi-engine/dsl/query.rb +24 -0
- data/lib/wongi-engine/dsl/rule.rb +84 -0
- data/lib/wongi-engine/enumerators.rb +21 -0
- data/lib/wongi-engine/error.rb +22 -0
- data/lib/wongi-engine/filter.rb +6 -0
- data/lib/wongi-engine/filter/asserting_test.rb +20 -0
- data/lib/wongi-engine/filter/equality_test.rb +36 -0
- data/lib/wongi-engine/filter/filter_test.rb +18 -0
- data/lib/wongi-engine/filter/greater_than_test.rb +36 -0
- data/lib/wongi-engine/filter/inequality_test.rb +36 -0
- data/lib/wongi-engine/filter/less_than_test.rb +36 -0
- data/lib/wongi-engine/graph.rb +73 -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 +85 -0
- data/lib/wongi-engine/ruleset.rb +74 -0
- data/lib/wongi-engine/template.rb +78 -0
- data/lib/wongi-engine/token.rb +114 -0
- data/lib/wongi-engine/version.rb +5 -0
- data/lib/wongi-engine/wme.rb +89 -0
- data/lib/wongi-engine/wme_match_data.rb +34 -0
- data/spec/beta_node_spec.rb +29 -0
- data/spec/bug_specs/issue_4_spec.rb +141 -0
- data/spec/dataset_spec.rb +27 -0
- data/spec/dsl_spec.rb +9 -0
- data/spec/filter_specs/assert_test_spec.rb +102 -0
- data/spec/filter_specs/less_test_spec.rb +41 -0
- data/spec/generation_spec.rb +116 -0
- data/spec/high_level_spec.rb +378 -0
- data/spec/network_spec.rb +182 -0
- data/spec/overlay_spec.rb +61 -0
- data/spec/rule_specs/any_rule_spec.rb +75 -0
- data/spec/rule_specs/assign_spec.rb +88 -0
- data/spec/rule_specs/assuming_spec.rb +66 -0
- data/spec/rule_specs/maybe_rule_spec.rb +101 -0
- data/spec/rule_specs/ncc_spec.rb +258 -0
- data/spec/rule_specs/negative_rule_spec.rb +105 -0
- data/spec/ruleset_spec.rb +54 -0
- data/spec/simple_action_spec.rb +40 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/wme_spec.rb +83 -0
- data/wongi-engine.gemspec +40 -0
- metadata +212 -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,85 @@
|
|
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
|
+
def full_dump io = $stdout
|
22
|
+
|
23
|
+
alpha_hash.each_value do |alpha|
|
24
|
+
io.puts "ALPHA #{alpha.template}"
|
25
|
+
alpha.wmes.each do |wme|
|
26
|
+
dump_wme wme, io
|
27
|
+
end
|
28
|
+
end
|
29
|
+
dump_beta beta_top, io
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def token_lineage token
|
36
|
+
result = []
|
37
|
+
while token.parent
|
38
|
+
result << token.parent
|
39
|
+
token = token.parent
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
def dump_wme wme, io
|
45
|
+
io.puts "\tWME: #{wme.object_id} #{wme}"
|
46
|
+
wme.tokens.each { |token| io.puts "\t\tTOKEN #{token.object_id}" }
|
47
|
+
io.puts "\tGENERATING:" unless wme.generating_tokens.empty?
|
48
|
+
wme.generating_tokens.each { |token| io.puts "\t\tTOKEN #{token.object_id}" }
|
49
|
+
end
|
50
|
+
|
51
|
+
def dump_beta beta, io
|
52
|
+
case beta
|
53
|
+
when BetaMemory
|
54
|
+
dump_beta_memory beta, io
|
55
|
+
when NccNode
|
56
|
+
dump_ncc beta, io
|
57
|
+
else
|
58
|
+
io.puts "BETA #{beta.object_id} #{beta.class} : TODO"
|
59
|
+
|
60
|
+
end
|
61
|
+
io.puts "\tCHILDREN: #{beta.children.map(&:object_id).join ", "}"
|
62
|
+
beta.children.each { |child| dump_beta child, io } unless beta.children.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def dump_beta_memory beta, io
|
66
|
+
io.puts "BETA MEMORY #{beta.object_id}"
|
67
|
+
beta.tokens.each { |token|
|
68
|
+
io.puts "\tTOKEN #{token.object_id} [#{token_lineage(token).map(&:object_id).map(&:to_s).join(" - ")}]"
|
69
|
+
token.wmes.each { |wme| io.puts "\t\tWME " + wme.object_id.to_s }
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def dump_ncc beta, io
|
74
|
+
io.puts "NCC #{beta.object_id}"
|
75
|
+
beta.tokens.each { |token|
|
76
|
+
io.puts "\tTOKEN #{token.object_id} [#{token_lineage(token).map(&:object_id).map(&:to_s).join(" - ")}]"
|
77
|
+
token.wmes.each { |wme| io.puts "\t\tWME " + wme.object_id.to_s }
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Wongi
|
2
|
+
module Engine
|
3
|
+
class Ruleset
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def [] name
|
8
|
+
raise Error, "undefined ruleset #{name}" unless rulesets.has_key?( name )
|
9
|
+
rulesets[ name ]
|
10
|
+
end
|
11
|
+
|
12
|
+
def register name, ruleset
|
13
|
+
raise Error, "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 StandardError => e
|
40
|
+
e1 = Error.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 = DSL::Rule.new name
|
60
|
+
r.instance_eval &definition
|
61
|
+
@rules << r
|
62
|
+
r
|
63
|
+
end
|
64
|
+
|
65
|
+
def query name, &definition
|
66
|
+
r = DSL::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,78 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
Template = Struct.new( :subject, :predicate, :object ) do
|
4
|
+
|
5
|
+
def self.variable? thing
|
6
|
+
return false unless thing.is_a?(Symbol)
|
7
|
+
thing[0] >= 'A' && thing[0] <= 'Z'
|
8
|
+
end
|
9
|
+
|
10
|
+
# TODO: reintroduce Network#import when bringing back RDF support
|
11
|
+
|
12
|
+
def root?
|
13
|
+
subject == :_ && predicate == :_ && object == :_
|
14
|
+
end
|
15
|
+
|
16
|
+
def variables
|
17
|
+
[].tap do |a|
|
18
|
+
a << subject if Template.variable?( subject )
|
19
|
+
a << predicate if Template.variable?( predicate )
|
20
|
+
a << object if Template.variable?( object )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
@hash ||= [subject.hash, predicate.hash, object.hash].hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.hash_for *args
|
29
|
+
args.map( &:hash ).hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def == other
|
33
|
+
other.is_a?(Template) && subject == other.subject && predicate == other.predicate && object == other.object
|
34
|
+
end
|
35
|
+
|
36
|
+
def =~ template
|
37
|
+
case template
|
38
|
+
when Template
|
39
|
+
( template.subject == :_ || template.subject == subject ) &&
|
40
|
+
( template.predicate == :_ || template.predicate == predicate ) &&
|
41
|
+
( template.object == :_ || template.object == object )
|
42
|
+
else
|
43
|
+
raise Error, "templates can only match other templates"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"<~#{subject.inspect} #{predicate.inspect} #{object.inspect}>"
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
def resolve!(token)
|
56
|
+
s = if Template.variable?(subject)
|
57
|
+
raise DefinitionError, "unbound variable #{subject} in token #{token}" unless token.has_var?(subject)
|
58
|
+
token[subject]
|
59
|
+
else
|
60
|
+
subject
|
61
|
+
end
|
62
|
+
p = if Template.variable?(predicate)
|
63
|
+
raise DefinitionError, "unbound variable #{predicate} in token #{token}" unless token.has_var?(predicate)
|
64
|
+
token[predicate]
|
65
|
+
else
|
66
|
+
predicate
|
67
|
+
end
|
68
|
+
o = if Template.variable?(object)
|
69
|
+
raise DefinitionError, "unbound variable #{object} in token #{token}" unless token.has_var?(object)
|
70
|
+
token[object]
|
71
|
+
else
|
72
|
+
object
|
73
|
+
end
|
74
|
+
[s, p, o]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class Token
|
4
|
+
|
5
|
+
include CoreExt
|
6
|
+
|
7
|
+
attr_reader :children
|
8
|
+
attr_reader :wme
|
9
|
+
attr_reader :node
|
10
|
+
attr_reader :overlay
|
11
|
+
attr_accessor :owner, :parent
|
12
|
+
attr_reader :neg_join_results
|
13
|
+
attr_reader :opt_join_results
|
14
|
+
attr_reader :ncc_results
|
15
|
+
attr_reader :generated_wmes
|
16
|
+
attr_predicate :optional
|
17
|
+
attr_predicate :deleted
|
18
|
+
|
19
|
+
def initialize node, token, wme, assignments
|
20
|
+
@node, @parent, @wme, @assignments = node, token, wme, assignments
|
21
|
+
@overlay = wme ? wme.overlay.highest(token.overlay) : (token ? token.overlay : node.rete.default_overlay)
|
22
|
+
@children = []
|
23
|
+
@deleted = false
|
24
|
+
@neg_join_results = []
|
25
|
+
@opt_join_results = []
|
26
|
+
@ncc_results = []
|
27
|
+
@generated_wmes = []
|
28
|
+
token.children << self if token
|
29
|
+
end
|
30
|
+
|
31
|
+
def ancestors
|
32
|
+
if parent
|
33
|
+
parent.ancestors.unshift parent
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def subst variable, value
|
40
|
+
@cached_assignments = nil
|
41
|
+
if @assignments.has_key? variable
|
42
|
+
@assignments[ variable ] = value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def assignments
|
47
|
+
@cached_assignments ||= all_assignments
|
48
|
+
end
|
49
|
+
|
50
|
+
def [] var
|
51
|
+
if a = assignments[ var ]
|
52
|
+
a.respond_to?(:call) ? a.call( self ) : a
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_var? x
|
57
|
+
assignments.has_key? x
|
58
|
+
end
|
59
|
+
|
60
|
+
# TODO ignore assignments?
|
61
|
+
def duplicate? other
|
62
|
+
self.parent.equal?(other.parent) && @wme.equal?(other.wme) && self.assignments == other.assignments
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
str = "TOKEN [ #{object_id} parent=#{parent ? parent.object_id : 'nil'} "
|
67
|
+
all_assignments.each_pair { |key, value| str << "#{key} => #{value} " }
|
68
|
+
str << "]"
|
69
|
+
str
|
70
|
+
end
|
71
|
+
|
72
|
+
def destroy
|
73
|
+
deleted!
|
74
|
+
end
|
75
|
+
|
76
|
+
def dispose!
|
77
|
+
parent.children.delete(self) if parent
|
78
|
+
neg_join_results.dup.each(&:unlink)
|
79
|
+
opt_join_results.dup.each(&:unlink)
|
80
|
+
@parent = nil
|
81
|
+
@wme = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# for neg feedback loop protection
|
85
|
+
def generated? wme
|
86
|
+
return true if generated_wmes.any? { |w| w == wme }
|
87
|
+
return children.any? { |t| t.generated? wme }
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def all_assignments
|
93
|
+
raise "Assignments is not a hash" unless @assignments.kind_of?( Hash )
|
94
|
+
if @parent
|
95
|
+
@parent.assignments.merge @assignments
|
96
|
+
else
|
97
|
+
@assignments
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class FakeToken < Token
|
104
|
+
def initialize token, wme, assignments
|
105
|
+
@parent, @wme, @assignments = token, wme, assignments
|
106
|
+
@children = []
|
107
|
+
@neg_join_results = []
|
108
|
+
@opt_join_results = []
|
109
|
+
@ncc_results = []
|
110
|
+
@generated_wmes = []
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
WME = Struct.new( :subject, :predicate, :object ) do
|
4
|
+
|
5
|
+
include CoreExt
|
6
|
+
|
7
|
+
attr_reader :rete
|
8
|
+
|
9
|
+
attr_reader :generating_tokens
|
10
|
+
attr_reader :neg_join_results, :opt_join_results
|
11
|
+
attr_accessor :overlay
|
12
|
+
attr_predicate :deleted
|
13
|
+
attr_predicate :manual
|
14
|
+
|
15
|
+
def initialize s, p, o, r = nil
|
16
|
+
|
17
|
+
manual!
|
18
|
+
|
19
|
+
@deleted = false
|
20
|
+
@alphas = []
|
21
|
+
@generating_tokens = []
|
22
|
+
@neg_join_results = []
|
23
|
+
@opt_join_results = []
|
24
|
+
|
25
|
+
@rete = r
|
26
|
+
|
27
|
+
# TODO: reintroduce Network#import when bringing back RDF support
|
28
|
+
super( s, p, o )
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def import_into r
|
33
|
+
self.class.new( subject, predicate, object, r ).tap do |wme|
|
34
|
+
wme.overlay = overlay
|
35
|
+
wme.manual = self.manual?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dup
|
40
|
+
self.class.new( subject, predicate, object, rete ).tap do |wme|
|
41
|
+
wme.overlay = overlay
|
42
|
+
wme.manual = self.manual?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def == other
|
47
|
+
subject == other.subject && predicate == other.predicate && object == other.object
|
48
|
+
end
|
49
|
+
|
50
|
+
def =~ template
|
51
|
+
raise Wongi::Engine::Error, "Cannot match a WME against a #{template.class}" unless Template === template
|
52
|
+
result = match_member( self.subject, template.subject ) & match_member( self.predicate, template.predicate ) & match_member( self.object, template.object )
|
53
|
+
if result.match?
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def generated?
|
59
|
+
!generating_tokens.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
"{#{subject.inspect} #{predicate.inspect} #{object.inspect}}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
inspect
|
68
|
+
end
|
69
|
+
|
70
|
+
def hash
|
71
|
+
@hash ||= [subject.hash, predicate.hash, object.hash].hash
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def match_member mine, theirs
|
77
|
+
result = WMEMatchData.new
|
78
|
+
if theirs == :_ || mine == theirs
|
79
|
+
result.match!
|
80
|
+
elsif Template.variable? theirs
|
81
|
+
result.match!
|
82
|
+
result[theirs] = mine
|
83
|
+
end
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|