mort666-wongi-engine 0.2.9
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.
- 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,46 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Action
|
3
|
+
class StatementGenerator < Base
|
4
|
+
|
5
|
+
def initialize template
|
6
|
+
@template = template
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute token
|
10
|
+
subject, predicate, object = @template.resolve!(token)
|
11
|
+
|
12
|
+
# link to rete here to ensure proper linking with token
|
13
|
+
wme = WME.new subject, predicate, object, rete
|
14
|
+
wme.manual = false
|
15
|
+
wme.overlay = token.overlay
|
16
|
+
|
17
|
+
production.tracer.trace( action: self, wme: wme ) if production.tracer
|
18
|
+
if existing = rete.exists?( wme )
|
19
|
+
generated = existing.generating_tokens.size
|
20
|
+
if generated > 0 && ! token.generated_wmes.include?( existing )
|
21
|
+
token.generated_wmes << existing
|
22
|
+
existing.generating_tokens << token
|
23
|
+
end
|
24
|
+
else
|
25
|
+
token.generated_wmes << wme
|
26
|
+
wme.generating_tokens << token
|
27
|
+
# this MUST be done after we link the wme and the token
|
28
|
+
# in order for neg rule invalidation to work
|
29
|
+
wme.overlay.assert wme
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def deexecute token
|
35
|
+
token.generated_wmes.reject( &:manual? ).inject( [] ) do |list, wme|
|
36
|
+
list.tap do |l|
|
37
|
+
wme.generating_tokens.delete token
|
38
|
+
l << wme if wme.generating_tokens.empty?
|
39
|
+
end
|
40
|
+
end.each do |wme|
|
41
|
+
wme.overlay.retract wme, automatic: true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Action
|
3
|
+
class TraceAction < Base
|
4
|
+
|
5
|
+
class DefaultTracer
|
6
|
+
attr_accessor :action
|
7
|
+
def trace args
|
8
|
+
case args[:action]
|
9
|
+
when TraceAction
|
10
|
+
if args[:token]
|
11
|
+
action.io.puts "EXECUTED RULE #{args[:action].rule.name} WITH #{args[:token]}"
|
12
|
+
else
|
13
|
+
action.io.puts "EXECUTED RULE #{args[:action].rule.name}"
|
14
|
+
end
|
15
|
+
when StatementGenerator
|
16
|
+
action.io.puts "GENERATED #{args[:wme]}" if action.generation?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :io
|
22
|
+
attr_predicate :generation, :values
|
23
|
+
|
24
|
+
def initialize opts = { }
|
25
|
+
[:generation, :values, :tracer, :tracer_class, :io].each do |option|
|
26
|
+
if opts.has_key? option
|
27
|
+
instance_variable_set "@#{option}", opts[option]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@io ||= $stdout
|
31
|
+
@tracer ||= (@tracer_class || DefaultTracer).new
|
32
|
+
@tracer.action = self
|
33
|
+
end
|
34
|
+
|
35
|
+
def trace args
|
36
|
+
@tracer.trace args
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute token
|
40
|
+
production.tracer = self
|
41
|
+
if values?
|
42
|
+
trace action: self, token: token
|
43
|
+
else
|
44
|
+
trace action: self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL
|
3
|
+
class AnyRule
|
4
|
+
|
5
|
+
attr_reader :variants
|
6
|
+
|
7
|
+
def initialize &block
|
8
|
+
@variants = []
|
9
|
+
if block
|
10
|
+
instance_eval &block
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def option &block
|
15
|
+
var = VariantRule.new
|
16
|
+
var.instance_eval &block
|
17
|
+
variants << var
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile context
|
21
|
+
context.tap { |c| c.or_node(variants) }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
class VariantRule < Rule
|
27
|
+
def initialize name = nil
|
28
|
+
super
|
29
|
+
@current_section = :forall
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class UndefinedBaseRule < StandardError
|
4
|
+
def initialize rule_name
|
5
|
+
@rule_name = rule_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def message
|
9
|
+
"undefined production #@rule_name"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class AssumingClause
|
14
|
+
|
15
|
+
attr_reader :base_rule_name
|
16
|
+
|
17
|
+
def initialize base_rule_name
|
18
|
+
@base_rule_name = base_rule_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def compile context
|
22
|
+
base_production = context.rete.productions[base_rule_name]
|
23
|
+
raise UndefinedBaseRule.new(base_rule_name) unless base_production
|
24
|
+
raise DefinitionError.new("'assuming' cannot be preceded by other matchers") unless context.node.root?
|
25
|
+
raise StandardError.new("missing base context") unless base_production.compilation_context
|
26
|
+
base_production.compilation_context.dup
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Wongi::Engine::DSL
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@current_section = nil
|
6
|
+
@current_clause = nil
|
7
|
+
@clauses = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def build &definition
|
11
|
+
instance_eval &definition
|
12
|
+
@clauses.each do |c|
|
13
|
+
Generated.create_dsl_method c
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def section s
|
18
|
+
@current_section = s
|
19
|
+
end
|
20
|
+
|
21
|
+
def clause *c
|
22
|
+
@current_clause = c
|
23
|
+
end
|
24
|
+
|
25
|
+
def action klass = nil, &block
|
26
|
+
raise DefinitionError, "Cannot create an action without a clause" if @current_clause.nil?
|
27
|
+
@clauses << { section: @current_section, clause: @current_clause, action: klass || block }
|
28
|
+
@current_clause = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def body klass = nil, &block
|
32
|
+
raise DefinitionError, "Cannot create a body without a clause" if @current_clause.nil?
|
33
|
+
@clauses << { section: @current_section, clause: @current_clause, body: klass || block }
|
34
|
+
@current_clause = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def accept klass
|
38
|
+
raise DefinitionError, "Cannot create an acceptor without a clause" if @current_clause.nil?
|
39
|
+
@clauses << { section: @current_section, clause: @current_clause, accept: klass }
|
40
|
+
@current_clause = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Clause
|
3
|
+
class Assign
|
4
|
+
|
5
|
+
def initialize variable, &body
|
6
|
+
@variable, @body = variable, body
|
7
|
+
raise DefinitionError, "#{variable} is not a variable" unless Template.variable?(variable)
|
8
|
+
end
|
9
|
+
|
10
|
+
def compile context
|
11
|
+
context.tap { |c| c.assignment_node(@variable, @body) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Clause
|
3
|
+
Has = Struct.new(:subject, :predicate, :object, :time) do
|
4
|
+
include CoreExt
|
5
|
+
attr_predicate :debug
|
6
|
+
|
7
|
+
def initialize(s, p, o, options = { })
|
8
|
+
time = options[:time] || 0
|
9
|
+
@unsafe = options[:unsafe] || false
|
10
|
+
debug! if options[:debug]
|
11
|
+
raise "Cannot work with continuous time" unless time.integer?
|
12
|
+
raise "Cannot look into the future" if time > 0
|
13
|
+
super(s, p, o, time)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile(context)
|
17
|
+
tests, assignment = parse_variables(context)
|
18
|
+
context.tap { |c| c.join_node(self, tests, assignment) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"<+#{subject.inspect} #{predicate.inspect} #{object.inspect}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_variables(context)
|
28
|
+
tests = []
|
29
|
+
assignment_mapping = [:subject, :predicate, :object].map do |member|
|
30
|
+
value = send(member)
|
31
|
+
if Template.variable?(value)
|
32
|
+
if context.declares_variable?(value)
|
33
|
+
tests << BetaTest.new(member, value)
|
34
|
+
:_
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
else
|
39
|
+
:_
|
40
|
+
end
|
41
|
+
end
|
42
|
+
assignment = Template.new(*assignment_mapping)
|
43
|
+
assignment.variables.each { |v| context.declare(v) }
|
44
|
+
[tests, assignment]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Neg < Has
|
49
|
+
attr_reader :unsafe
|
50
|
+
def compile context
|
51
|
+
tests, assignment = parse_variables(context)
|
52
|
+
raise DefinitionError.new("Negative matches may not introduce new variables: #{assignment.variables}") unless assignment.root?
|
53
|
+
context.tap { |c| c.neg_node(self, tests, unsafe) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"<-#{subject.inspect} #{predicate.inspect} #{object.inspect}>"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Opt < Has
|
62
|
+
def compile context
|
63
|
+
tests, assignment = parse_variables(context)
|
64
|
+
context.tap { |c| c.opt_node(self, tests, assignment) }
|
65
|
+
end
|
66
|
+
def inspect
|
67
|
+
"<?#{subject.inspect} #{predicate.inspect} #{object.inspect}>"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Clause
|
3
|
+
class Gen
|
4
|
+
|
5
|
+
def initialize s, p, o
|
6
|
+
@triple = Template.new( s, p, o )
|
7
|
+
end
|
8
|
+
|
9
|
+
def import_into rete
|
10
|
+
generator = DSL::Action::StatementGenerator.new @triple
|
11
|
+
generator.rete = rete
|
12
|
+
generator
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL::Clause
|
3
|
+
class Generic
|
4
|
+
|
5
|
+
attr_accessor :name, :action, :rule
|
6
|
+
|
7
|
+
def initialize *args, &block
|
8
|
+
@args = args
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def import_into rete
|
13
|
+
if action.respond_to? :call
|
14
|
+
self
|
15
|
+
else
|
16
|
+
action.new( *@args, &@block ).tap do |a|
|
17
|
+
a.name = name if a.respond_to? :name=
|
18
|
+
a.rule = rule if a.respond_to? :rule=
|
19
|
+
a.rete = rete if a.respond_to? :rete=
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue StandardError => e
|
23
|
+
e1 = StandardError.new "error defining clause #{name} handled by #{action}: #{e}"
|
24
|
+
e1.set_backtrace e.backtrace
|
25
|
+
raise e1
|
26
|
+
end
|
27
|
+
|
28
|
+
def compile *args
|
29
|
+
action.call *args
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute *args
|
33
|
+
action.call *args
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Wongi::Engine::DSL
|
2
|
+
module Generated
|
3
|
+
|
4
|
+
def self.create_dsl_method extension
|
5
|
+
|
6
|
+
section = extension[:section]
|
7
|
+
clause = extension[:clause]
|
8
|
+
action = extension[:action]
|
9
|
+
body = extension[:body]
|
10
|
+
acceptor = extension[:accept]
|
11
|
+
|
12
|
+
define_method clause.first do |*args, &block|
|
13
|
+
|
14
|
+
raise "#{clause.first} can only be invoke in section #{section}, currently in #{@current_section}" if section != @current_section
|
15
|
+
|
16
|
+
if body
|
17
|
+
|
18
|
+
instance_exec *args, &body
|
19
|
+
|
20
|
+
elsif acceptor
|
21
|
+
|
22
|
+
accept acceptor.new( *args, &block )
|
23
|
+
|
24
|
+
elsif action
|
25
|
+
|
26
|
+
c = Clause::Generic.new *args, &block
|
27
|
+
c.name = clause.first
|
28
|
+
c.action = action
|
29
|
+
c.rule = self
|
30
|
+
accept c
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
clause[1..-1].each do |al|
|
37
|
+
alias_method al, clause.first
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL
|
3
|
+
class Query < Rule
|
4
|
+
|
5
|
+
def search_on *terms
|
6
|
+
terms.each { |term| parameters << term }
|
7
|
+
end
|
8
|
+
|
9
|
+
def import_into model
|
10
|
+
super.tap { |copy| copy.search_on *parameters }
|
11
|
+
end
|
12
|
+
|
13
|
+
def parameters
|
14
|
+
@parameters ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def install( rete )
|
18
|
+
rete.install_query( self )
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
module DSL
|
3
|
+
class Rule
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
include Generated
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def section s, *aliases
|
12
|
+
unless sections.include?(s)
|
13
|
+
sections << s
|
14
|
+
define_method s do |&d|
|
15
|
+
@current_section = s
|
16
|
+
instance_eval &d
|
17
|
+
end
|
18
|
+
aliases.each { |a| alias_method a, s }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def sections
|
23
|
+
@sections ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
section :forall, :for_all
|
29
|
+
section :make, :do!
|
30
|
+
|
31
|
+
def initialize name
|
32
|
+
@name = name
|
33
|
+
@current_section = nil
|
34
|
+
Rule.sections.each { |section| acceptors[section] ||= [] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def acceptors
|
38
|
+
@acceptors ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def conditions
|
42
|
+
acceptors[:forall] ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def conditions= c
|
46
|
+
acceptors[:forall] = c
|
47
|
+
end
|
48
|
+
|
49
|
+
def actions
|
50
|
+
acceptors[:make] ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def actions= a
|
54
|
+
acceptors[:make] = a
|
55
|
+
end
|
56
|
+
|
57
|
+
def import_into rete
|
58
|
+
self.class.new( @name ).tap do |copy|
|
59
|
+
copy.conditions = conditions
|
60
|
+
|
61
|
+
copy.actions = actions.map do |action|
|
62
|
+
if action.respond_to? :import_into
|
63
|
+
action.import_into(rete)
|
64
|
+
else
|
65
|
+
action
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def install( rete )
|
72
|
+
rete.install_rule( self )
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def accept stuff
|
78
|
+
acceptors[@current_section] << stuff
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|