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