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.
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