wongi-engine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +349 -0
  5. data/Rakefile +2 -0
  6. data/examples/ex01.rb +23 -0
  7. data/examples/ex02.rb +36 -0
  8. data/examples/graphviz.rb +15 -0
  9. data/examples/timeline.rb +48 -0
  10. data/lib/wongi-engine.rb +22 -0
  11. data/lib/wongi-engine/alpha_memory.rb +46 -0
  12. data/lib/wongi-engine/beta.rb +10 -0
  13. data/lib/wongi-engine/beta/beta_memory.rb +48 -0
  14. data/lib/wongi-engine/beta/beta_node.rb +164 -0
  15. data/lib/wongi-engine/beta/filter_node.rb +109 -0
  16. data/lib/wongi-engine/beta/join_node.rb +127 -0
  17. data/lib/wongi-engine/beta/ncc_node.rb +46 -0
  18. data/lib/wongi-engine/beta/ncc_partner.rb +43 -0
  19. data/lib/wongi-engine/beta/neg_node.rb +58 -0
  20. data/lib/wongi-engine/beta/optional_node.rb +43 -0
  21. data/lib/wongi-engine/beta/or_node.rb +76 -0
  22. data/lib/wongi-engine/beta/production_node.rb +31 -0
  23. data/lib/wongi-engine/core_ext.rb +57 -0
  24. data/lib/wongi-engine/dsl.rb +112 -0
  25. data/lib/wongi-engine/dsl/action.rb +12 -0
  26. data/lib/wongi-engine/dsl/actions/error_generator.rb +42 -0
  27. data/lib/wongi-engine/dsl/actions/simple_action.rb +23 -0
  28. data/lib/wongi-engine/dsl/actions/simple_collector.rb +51 -0
  29. data/lib/wongi-engine/dsl/actions/statement_generator.rb +52 -0
  30. data/lib/wongi-engine/dsl/actions/trace_action.rb +52 -0
  31. data/lib/wongi-engine/dsl/any_rule.rb +48 -0
  32. data/lib/wongi-engine/dsl/dsl_builder.rb +44 -0
  33. data/lib/wongi-engine/dsl/dsl_extensions.rb +43 -0
  34. data/lib/wongi-engine/dsl/extension_clause.rb +36 -0
  35. data/lib/wongi-engine/dsl/generation_clause.rb +15 -0
  36. data/lib/wongi-engine/dsl/generic_production_rule.rb +78 -0
  37. data/lib/wongi-engine/dsl/ncc_production_rule.rb +21 -0
  38. data/lib/wongi-engine/dsl/production_rule.rb +4 -0
  39. data/lib/wongi-engine/dsl/query.rb +24 -0
  40. data/lib/wongi-engine/graph.rb +71 -0
  41. data/lib/wongi-engine/model_context.rb +13 -0
  42. data/lib/wongi-engine/network.rb +416 -0
  43. data/lib/wongi-engine/network/collectable.rb +42 -0
  44. data/lib/wongi-engine/network/debug.rb +25 -0
  45. data/lib/wongi-engine/ruleset.rb +74 -0
  46. data/lib/wongi-engine/template.rb +111 -0
  47. data/lib/wongi-engine/token.rb +137 -0
  48. data/lib/wongi-engine/version.rb +5 -0
  49. data/lib/wongi-engine/wme.rb +134 -0
  50. data/lib/wongi-engine/wme_match_data.rb +34 -0
  51. data/spec/dataset_spec.rb +26 -0
  52. data/spec/dsl_spec.rb +9 -0
  53. data/spec/high_level_spec.rb +341 -0
  54. data/spec/ruleset_spec.rb +54 -0
  55. data/spec/simple_action_spec.rb +40 -0
  56. data/spec/spec_helper.rb +1 -0
  57. data/spec/wme_spec.rb +83 -0
  58. data/wongi-engine.gemspec +19 -0
  59. 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,5 @@
1
+ module Wongi
2
+ module Engine
3
+ VERSION = "0.0.1"
4
+ end
5
+ 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