mini_kraken 0.2.04 → 0.3.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +16 -16
- data/lib/mini_kraken/atomic/all_atomic.rb +1 -0
- data/lib/mini_kraken/atomic/atomic_term.rb +32 -17
- data/lib/mini_kraken/atomic/k_integer.rb +0 -4
- data/lib/mini_kraken/atomic/k_string.rb +17 -0
- data/lib/mini_kraken/atomic/k_symbol.rb +0 -6
- data/lib/mini_kraken/composite/all_composite.rb +4 -0
- data/lib/mini_kraken/composite/composite_term.rb +2 -18
- data/lib/mini_kraken/composite/cons_cell.rb +178 -11
- data/lib/mini_kraken/composite/cons_cell_visitor.rb +12 -64
- data/lib/mini_kraken/composite/list.rb +32 -0
- data/lib/mini_kraken/core/all_core.rb +8 -0
- data/lib/mini_kraken/core/any_value.rb +31 -7
- data/lib/mini_kraken/core/arity.rb +69 -0
- data/lib/mini_kraken/core/association.rb +29 -4
- data/lib/mini_kraken/core/association_copy.rb +50 -0
- data/lib/mini_kraken/core/base_term.rb +13 -0
- data/lib/mini_kraken/core/blackboard.rb +315 -0
- data/lib/mini_kraken/core/bookmark.rb +46 -0
- data/lib/mini_kraken/core/context.rb +624 -0
- data/lib/mini_kraken/core/duck_fiber.rb +21 -19
- data/lib/mini_kraken/core/entry.rb +40 -0
- data/lib/mini_kraken/core/fail.rb +20 -18
- data/lib/mini_kraken/core/fusion.rb +29 -0
- data/lib/mini_kraken/core/goal.rb +20 -29
- data/lib/mini_kraken/core/log_var.rb +4 -30
- data/lib/mini_kraken/core/log_var_ref.rb +72 -48
- data/lib/mini_kraken/core/nullary_relation.rb +2 -9
- data/lib/mini_kraken/core/parametrized_term.rb +61 -0
- data/lib/mini_kraken/core/relation.rb +14 -28
- data/lib/mini_kraken/core/scope.rb +67 -0
- data/lib/mini_kraken/core/solver_adapter.rb +58 -0
- data/lib/mini_kraken/core/specification.rb +48 -0
- data/lib/mini_kraken/core/succeed.rb +21 -17
- data/lib/mini_kraken/core/symbol_table.rb +137 -0
- data/lib/mini_kraken/core/term.rb +15 -4
- data/lib/mini_kraken/glue/dsl.rb +35 -69
- data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
- data/lib/mini_kraken/rela/all_rela.rb +8 -0
- data/lib/mini_kraken/rela/binary_relation.rb +30 -0
- data/lib/mini_kraken/rela/conde.rb +146 -0
- data/lib/mini_kraken/rela/conj2.rb +65 -0
- data/lib/mini_kraken/rela/def_relation.rb +64 -0
- data/lib/mini_kraken/rela/disj2.rb +70 -0
- data/lib/mini_kraken/rela/fresh.rb +98 -0
- data/lib/mini_kraken/{core → rela}/goal_relation.rb +6 -8
- data/lib/mini_kraken/rela/unify.rb +258 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/atomic/atomic_term_spec.rb +23 -20
- data/spec/atomic/k_symbol_spec.rb +0 -5
- data/spec/composite/cons_cell_spec.rb +116 -0
- data/spec/composite/cons_cell_visitor_spec.rb +16 -3
- data/spec/composite/list_spec.rb +50 -0
- data/spec/core/any_value_spec.rb +52 -0
- data/spec/core/arity_spec.rb +91 -0
- data/spec/core/association_copy_spec.rb +69 -0
- data/spec/core/association_spec.rb +25 -0
- data/spec/core/blackboard_spec.rb +287 -0
- data/spec/core/bookmark_spec.rb +40 -0
- data/spec/core/context_spec.rb +221 -0
- data/spec/core/core_spec.rb +40 -0
- data/spec/core/duck_fiber_spec.rb +22 -46
- data/spec/core/fail_spec.rb +5 -6
- data/spec/core/goal_spec.rb +20 -11
- data/spec/core/log_var_ref_spec.rb +80 -5
- data/spec/core/log_var_spec.rb +35 -6
- data/spec/core/nullary_relation_spec.rb +33 -0
- data/spec/core/parametrized_tem_spec.rb +39 -0
- data/spec/core/relation_spec.rb +33 -0
- data/spec/core/scope_spec.rb +73 -0
- data/spec/core/solver_adapter_spec.rb +70 -0
- data/spec/core/specification_spec.rb +43 -0
- data/spec/core/succeed_spec.rb +5 -5
- data/spec/core/symbol_table_spec.rb +142 -0
- data/spec/glue/dsl_chap1_spec.rb +88 -99
- data/spec/glue/dsl_chap2_spec.rb +59 -41
- data/spec/glue/run_star_expression_spec.rb +69 -896
- data/spec/{core → rela}/conde_spec.rb +50 -46
- data/spec/rela/conj2_spec.rb +123 -0
- data/spec/rela/def_relation_spec.rb +119 -0
- data/spec/rela/disj2_spec.rb +117 -0
- data/spec/rela/fresh_spec.rb +147 -0
- data/spec/rela/unify_spec.rb +369 -0
- data/spec/support/factory_atomic.rb +7 -0
- data/spec/support/factory_composite.rb +21 -0
- metadata +71 -48
- data/lib/mini_kraken/core/association_walker.rb +0 -183
- data/lib/mini_kraken/core/base_arg.rb +0 -10
- data/lib/mini_kraken/core/binary_relation.rb +0 -63
- data/lib/mini_kraken/core/composite_goal.rb +0 -46
- data/lib/mini_kraken/core/conde.rb +0 -143
- data/lib/mini_kraken/core/conj2.rb +0 -79
- data/lib/mini_kraken/core/def_relation.rb +0 -53
- data/lib/mini_kraken/core/designation.rb +0 -55
- data/lib/mini_kraken/core/disj2.rb +0 -72
- data/lib/mini_kraken/core/environment.rb +0 -73
- data/lib/mini_kraken/core/equals.rb +0 -191
- data/lib/mini_kraken/core/formal_arg.rb +0 -22
- data/lib/mini_kraken/core/formal_ref.rb +0 -25
- data/lib/mini_kraken/core/freshness.rb +0 -45
- data/lib/mini_kraken/core/goal_arg.rb +0 -12
- data/lib/mini_kraken/core/goal_template.rb +0 -102
- data/lib/mini_kraken/core/outcome.rb +0 -63
- data/lib/mini_kraken/core/tap.rb +0 -46
- data/lib/mini_kraken/core/vocabulary.rb +0 -446
- data/lib/mini_kraken/glue/fresh_env.rb +0 -108
- data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
- data/spec/core/association_walker_spec.rb +0 -194
- data/spec/core/conj2_spec.rb +0 -116
- data/spec/core/def_relation_spec.rb +0 -99
- data/spec/core/disj2_spec.rb +0 -100
- data/spec/core/environment_spec.rb +0 -144
- data/spec/core/equals_spec.rb +0 -319
- data/spec/core/goal_template_spec.rb +0 -74
- data/spec/core/outcome_spec.rb +0 -56
- data/spec/core/vocabulary_spec.rb +0 -220
- data/spec/glue/fresh_env_factory_spec.rb +0 -99
- data/spec/glue/fresh_env_spec.rb +0 -62
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
require_relative '../core/context'
|
6
|
+
require_relative '../core/duck_fiber'
|
7
|
+
require_relative 'goal_relation'
|
8
|
+
|
9
|
+
module MiniKraken
|
10
|
+
module Rela
|
11
|
+
# The conjunction is a relation that accepts only two goals as its
|
12
|
+
# arguments. It succeeds if and only if both its goal arguments succeeds.
|
13
|
+
class Conj2 < GoalRelation
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
# Default initialization
|
17
|
+
def initialize
|
18
|
+
super('conj2', 2)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param actuals [Array<Core::Term>] A two-elements array
|
22
|
+
# @param ctx [Core::Context] A context object
|
23
|
+
# @return [Fiber<Core::Context>] A Fiber that yields Context objects
|
24
|
+
def solver_for(actuals, ctx)
|
25
|
+
g1, g2 = *validated_args(actuals)
|
26
|
+
Fiber.new { conjunction(g1, g2, ctx) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Yields [Core::Context, NilClass] result of the conjunction
|
30
|
+
# @param g1 [Goal] First goal argument
|
31
|
+
# @param g2 [Goal] Second goal argument
|
32
|
+
# @param ctx [Core::Context] A ctxabulary object
|
33
|
+
def conjunction(g1, g2, ctx)
|
34
|
+
# require 'debug'
|
35
|
+
if g1.relation.kind_of?(Core::Fail) || g2.relation.kind_of?(Core::Fail)
|
36
|
+
Fiber.yield ctx.failed!
|
37
|
+
else
|
38
|
+
outcome1 = outcome2 = nil
|
39
|
+
fiber1 = g1.achieve(ctx)
|
40
|
+
|
41
|
+
loop do
|
42
|
+
outcome1 = fiber1.resume(ctx)
|
43
|
+
break if outcome1.nil?
|
44
|
+
|
45
|
+
if outcome1.success?
|
46
|
+
fiber2 = g2.achieve(ctx)
|
47
|
+
loop do
|
48
|
+
outcome2 = fiber2.resume(ctx)
|
49
|
+
break if outcome2.nil?
|
50
|
+
|
51
|
+
Fiber.yield outcome2
|
52
|
+
end
|
53
|
+
else
|
54
|
+
Fiber.yield outcome1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Fiber.yield nil
|
60
|
+
end
|
61
|
+
end # class
|
62
|
+
|
63
|
+
Conj2.instance.freeze
|
64
|
+
end # module
|
65
|
+
end # module
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require_relative '../core/relation'
|
5
|
+
require_relative '../core/entry'
|
6
|
+
|
7
|
+
module MiniKraken
|
8
|
+
module Rela
|
9
|
+
# A user-defined relation with:
|
10
|
+
# - a user-defined name,
|
11
|
+
# - a ordered list of generic formal arguments, and;
|
12
|
+
# - a goal template expression.
|
13
|
+
class DefRelation < Core::Relation
|
14
|
+
include Core::Entry # Add behaviour of symbol table entries
|
15
|
+
|
16
|
+
# @return [Array<FormalArg>] formal arguments of this DefRelation
|
17
|
+
attr_reader :formals
|
18
|
+
|
19
|
+
# @return [Term] Expression to be fulfilled and parametrized with formals
|
20
|
+
attr_reader :expression
|
21
|
+
|
22
|
+
# @param aName [String] name of def relation
|
23
|
+
# @param anExpression [Term]
|
24
|
+
# @param theFormals [Array<String>]
|
25
|
+
def initialize(aName, anExpression, theFormals)
|
26
|
+
@formals = validated_formals(theFormals)
|
27
|
+
formal_vars = formals.map { |nm| Core::LogVarRef.new(nm) }
|
28
|
+
raw_expression = validated_expression(anExpression)
|
29
|
+
@expression = replace_expression(raw_expression, theFormals, formal_vars)
|
30
|
+
super(aName, formals.size)
|
31
|
+
freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param actuals [Array<Term>] A two-elements array
|
35
|
+
# @param ctx [Context] A Context object
|
36
|
+
# @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
|
37
|
+
def solver_for(actuals, ctx)
|
38
|
+
actual_expr = replace_expression(expression, formals, actuals)
|
39
|
+
actual_expr.achieve(ctx)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def validated_formals(theFormals)
|
45
|
+
# Make the formal names unique, to avoid name collision
|
46
|
+
theFormals.map { |name| "#{name}_#{SecureRandom.uuid}" }
|
47
|
+
end
|
48
|
+
|
49
|
+
def validated_expression(aGoalTemplate)
|
50
|
+
raise StandardError unless aGoalTemplate
|
51
|
+
|
52
|
+
aGoalTemplate
|
53
|
+
end
|
54
|
+
|
55
|
+
# With the given expression, create a new expression where
|
56
|
+
# each allusion to original variable is replaced by the
|
57
|
+
# by its corresponding actual value.
|
58
|
+
def replace_expression(anExpression, original, actual)
|
59
|
+
raw_pairs = original.zip(actual) # [original, actual]
|
60
|
+
anExpression.dup_cond(raw_pairs.to_h)
|
61
|
+
end
|
62
|
+
end # class
|
63
|
+
end # module
|
64
|
+
end # module
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
require_relative '../core/context'
|
6
|
+
require_relative '../core/duck_fiber'
|
7
|
+
require_relative 'goal_relation'
|
8
|
+
|
9
|
+
module MiniKraken
|
10
|
+
module Rela
|
11
|
+
# The disjunction is a relation that accepts only goal(s) as its two
|
12
|
+
# arguments. It succeeds if at least one of its goal arguments succeeds.
|
13
|
+
class Disj2 < GoalRelation
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
# Default initialization
|
17
|
+
def initialize
|
18
|
+
super('disj2', 2)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param actuals [Array<Core::Term>] A two-elements array
|
22
|
+
# @param ctx [Core::Context] A context object
|
23
|
+
# @return [Fiber<Core::Context>] A Fiber that yields Context objects
|
24
|
+
def solver_for(actuals, ctx)
|
25
|
+
g1, g2 = *validated_args(actuals)
|
26
|
+
Fiber.new { disjunction(g1, g2, ctx) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Yields [Core::Context, NilClass] result of the disjunction
|
30
|
+
# @param g1 [Goal] First goal argument
|
31
|
+
# @param g2 [Goal] Second goal argument
|
32
|
+
# @param ctx [Core::Context] A ctxabulary object
|
33
|
+
def disjunction(g1, g2, ctx)
|
34
|
+
# require 'debug'
|
35
|
+
if g1.relation.kind_of?(Core::Fail) && g2.relation.kind_of?(Core::Fail)
|
36
|
+
Fiber.yield ctx.failed!
|
37
|
+
else
|
38
|
+
ctx.place_bt_point
|
39
|
+
outcome1 = nil
|
40
|
+
outcome2 = nil
|
41
|
+
f1 = g1.achieve(ctx)
|
42
|
+
loop do
|
43
|
+
outcome1 = f1.resume(ctx)
|
44
|
+
break if outcome1.nil?
|
45
|
+
|
46
|
+
if outcome1.success?
|
47
|
+
Fiber.yield outcome1
|
48
|
+
ctx.next_alternative
|
49
|
+
end
|
50
|
+
end
|
51
|
+
f2 = g2.achieve(ctx)
|
52
|
+
loop do
|
53
|
+
outcome2 = f2.resume(ctx)
|
54
|
+
break if outcome2.nil?
|
55
|
+
|
56
|
+
if outcome2.success?
|
57
|
+
Fiber.yield outcome2
|
58
|
+
ctx.next_alternative
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
ctx.retract_bt_point
|
64
|
+
Fiber.yield nil
|
65
|
+
end
|
66
|
+
end # class
|
67
|
+
|
68
|
+
Disj2.instance.freeze
|
69
|
+
end # module
|
70
|
+
end # module
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require_relative '../core/solver_adapter'
|
5
|
+
require_relative '../atomic/k_string'
|
6
|
+
require_relative 'conj2'
|
7
|
+
require_relative 'goal_relation'
|
8
|
+
|
9
|
+
module MiniKraken
|
10
|
+
module Rela
|
11
|
+
# A specialized relation that accepts a variable names(s) and a subgoal
|
12
|
+
# as its arguments.
|
13
|
+
class Fresh < GoalRelation
|
14
|
+
include Singleton
|
15
|
+
# Default initialization
|
16
|
+
def initialize
|
17
|
+
super('fresh', 2)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.build_goal(names, subgoals)
|
21
|
+
var_names = nil
|
22
|
+
|
23
|
+
case names
|
24
|
+
when String
|
25
|
+
var_names = Atomic::KString.new(names)
|
26
|
+
|
27
|
+
when Array
|
28
|
+
var_names = names.map do |nm|
|
29
|
+
nm.is_a?(String) ? Atomic::KString.new(nm) : nm
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
nested_goal = compose_goals(subgoals)
|
34
|
+
Core::Goal.new(instance, [var_names, nested_goal])
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.compose_goals(subgoals)
|
38
|
+
nested_goal = nil
|
39
|
+
|
40
|
+
case subgoals
|
41
|
+
when Core::Goal
|
42
|
+
nested_goal = subgoals
|
43
|
+
|
44
|
+
when Array
|
45
|
+
goal_array = subgoals
|
46
|
+
loop do
|
47
|
+
conjunctions = []
|
48
|
+
goal_array.each_slice(2) do |uno_duo|
|
49
|
+
if uno_duo.size == 2
|
50
|
+
conjunctions << Core::Goal.new(Conj2.instance, uno_duo)
|
51
|
+
else
|
52
|
+
conjunctions << uno_duo[0]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if conjunctions.size == 1
|
56
|
+
nested_goal = conjunctions[0]
|
57
|
+
break
|
58
|
+
end
|
59
|
+
goal_array = conjunctions
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
nested_goal
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param actuals [Array<Array<KString>, Core::Term>] A two-elements array
|
67
|
+
# First element is an array of variable names to create.
|
68
|
+
# Second is a sub-goal object
|
69
|
+
# @param ctx [Core::Context] A context object
|
70
|
+
# @return [Fiber<Core::Context>] A Fiber that yields Context objects
|
71
|
+
def solver_for(actuals, ctx)
|
72
|
+
k_names = actuals.shift
|
73
|
+
# require 'debug'
|
74
|
+
subgoal = validated_args(actuals).first
|
75
|
+
|
76
|
+
ctx.enter_scope(Core::Scope.new)
|
77
|
+
if k_names.kind_of?(Atomic::KString)
|
78
|
+
ctx.add_vars(k_names.value)
|
79
|
+
else
|
80
|
+
# ... Array of KString
|
81
|
+
names = k_names.map(&:value)
|
82
|
+
ctx.add_vars(names)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Wrap the subgoal's solver by an adapter
|
86
|
+
orig_solver = subgoal.achieve(ctx)
|
87
|
+
Core::SolverAdapter.new(orig_solver) do |adp, context|
|
88
|
+
# puts "Adaptee #{adp.adaptee}"
|
89
|
+
result = adp.adaptee.resume(context)
|
90
|
+
context.leave_scope if result.nil?
|
91
|
+
result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end # class
|
95
|
+
|
96
|
+
Fresh.instance.freeze
|
97
|
+
end # module
|
98
|
+
end # module
|
@@ -1,21 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../core/goal'
|
4
|
+
require_relative '../core/relation'
|
4
5
|
|
5
6
|
module MiniKraken
|
6
|
-
module
|
7
|
+
module Rela
|
7
8
|
# A specialization of a relation that accepts only goal(s)
|
8
9
|
# as its arguments.
|
9
|
-
class GoalRelation < Relation
|
10
|
-
def arity
|
11
|
-
2
|
12
|
-
end
|
13
|
-
|
10
|
+
class GoalRelation < Core::Relation
|
14
11
|
protected
|
15
12
|
|
13
|
+
# Validate that actuals
|
16
14
|
def validated_args(actuals)
|
17
15
|
actuals.each do |arg|
|
18
|
-
unless arg.kind_of?(Goal)
|
16
|
+
unless arg.kind_of?(Core::Goal)
|
19
17
|
prefix = "#{name} expects goal as argument, found a "
|
20
18
|
raise StandardError, prefix + "'#{arg.class}': #{arg}"
|
21
19
|
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require_relative 'binary_relation'
|
5
|
+
|
6
|
+
require_relative '../core/duck_fiber'
|
7
|
+
require_relative '../core/log_var_ref'
|
8
|
+
require_relative '../atomic/atomic_term'
|
9
|
+
require_relative '../composite/all_composite'
|
10
|
+
|
11
|
+
|
12
|
+
module MiniKraken
|
13
|
+
module Rela
|
14
|
+
# Corresponds to the '==' relation in canonical miniKanren implementation
|
15
|
+
# in Scheme. Implements the core of the unification algorithm.
|
16
|
+
class Unify < BinaryRelation
|
17
|
+
include Singleton
|
18
|
+
|
19
|
+
symmetric # Unify relation is symmetric ("First Law of ==")
|
20
|
+
|
21
|
+
# Constructor. Initialize the name of the relation
|
22
|
+
def initialize
|
23
|
+
super('unify')
|
24
|
+
freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param actuals [Array<Term>] A two-elements array
|
28
|
+
# @param ctx [Context] A context object
|
29
|
+
# @return [Fiber<Context>] A DuckFiber instance that yields one Context.
|
30
|
+
def solver_for(actuals, ctx)
|
31
|
+
arg1, arg2 = *actuals
|
32
|
+
# context = unification(arg1, arg2, ctx)
|
33
|
+
# Core::DuckFiber.new(-> { context })
|
34
|
+
Core::DuckFiber.new(-> { unification(arg1, arg2, ctx) })
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param arg1 [Term]
|
38
|
+
# @param arg2 [Term]
|
39
|
+
# @param ctx [Context] A context object
|
40
|
+
# @return [Context] The updated context
|
41
|
+
def unification(arg1, arg2, ctx)
|
42
|
+
return ctx.succeeded! if arg1.equal?(arg2)
|
43
|
+
return ctx.failed! if arg1.nil? || arg2.nil?
|
44
|
+
|
45
|
+
new_arg1, new_arg2 = commute_cond(arg1, arg2, ctx)
|
46
|
+
do_unification(new_arg1, new_arg2, ctx)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def do_unification(arg1, arg2, ctx)
|
52
|
+
table = [
|
53
|
+
# cond1 cond2 selector
|
54
|
+
[kind_of(Atomic::AtomicTerm), kind_of(Atomic::AtomicTerm),
|
55
|
+
:unify_atomic_terms],
|
56
|
+
[kind_of(Composite::CompositeTerm), kind_of(Atomic::AtomicTerm),
|
57
|
+
:unify_composite_atomic],
|
58
|
+
[kind_of(Composite::CompositeTerm), kind_of(Composite::CompositeTerm),
|
59
|
+
:unify_composite_terms],
|
60
|
+
[kind_of(Core::LogVarRef), kind_of(Atomic::AtomicTerm),
|
61
|
+
:unify_ref_atomic],
|
62
|
+
[kind_of(Core::LogVarRef), kind_of(Composite::CompositeTerm),
|
63
|
+
:unify_ref_composite],
|
64
|
+
[kind_of(Core::LogVarRef), kind_of(Core::LogVarRef),
|
65
|
+
:unify_references]
|
66
|
+
]
|
67
|
+
|
68
|
+
# require 'debug'
|
69
|
+
|
70
|
+
table.each do |(cond1, cond2, selector)|
|
71
|
+
if cell_success(arg1, cond1, ctx) &&
|
72
|
+
cell_success(arg2, cond2, ctx)
|
73
|
+
return send(selector, arg1, arg2, ctx)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
ctx.failed
|
78
|
+
end
|
79
|
+
|
80
|
+
=begin
|
81
|
+
# table: Commute
|
82
|
+
# |arg1 | arg2 | arg2.ground? || Commute |
|
83
|
+
# | isa? Atomic | isa? Atomic | dont_care || Yes |
|
84
|
+
# | isa? Atomic | isa? CompositeTerm | dont_care || Yes |
|
85
|
+
# | isa? Atomic | isa? LogVarRef | dont_care || Yes |
|
86
|
+
# | isa? CompositeTerm | isa? Atomic | true || No |
|
87
|
+
# | isa? CompositeTerm | isa? CompositeTerm | false || Yes |
|
88
|
+
# | isa? CompositeTerm | isa? CompositeTerm | true || No |
|
89
|
+
# | isa? CompositeTerm | isa? LogVarRef | dont_care || Yes |
|
90
|
+
# | isa? LogVarRef | isa? Atomic | dont_care || No |
|
91
|
+
# | isa? LogVarRef | isa? CompositeTerm | dont_care || No |
|
92
|
+
# | isa? LogVarRef | isa? LogVarRef | false || Yes |
|
93
|
+
# | isa? LogVarRef | isa? LogVarRef | true || No |
|
94
|
+
=end
|
95
|
+
|
96
|
+
def weight_arg(arg, ctx)
|
97
|
+
case arg
|
98
|
+
when Atomic::AtomicTerm
|
99
|
+
1
|
100
|
+
when Composite::CompositeTerm
|
101
|
+
2
|
102
|
+
when Core::LogVarRef
|
103
|
+
# Move unbound argument to the right...
|
104
|
+
arg.unbound?(ctx) ? 3 : 4
|
105
|
+
else
|
106
|
+
raise StandardError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def kind_of(aClass)
|
111
|
+
->(ar, _) { ar.kind_of?(aClass) }
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def cell_success(arg, cond, ctx)
|
117
|
+
case cond
|
118
|
+
when Class
|
119
|
+
arg.kind_of?(cond.class)
|
120
|
+
when Proc
|
121
|
+
cond.call(arg, ctx)
|
122
|
+
else
|
123
|
+
raise StandardError
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Unification of two atomic terms
|
128
|
+
# @param arg1 [Atomic::AtomicTerm]
|
129
|
+
# @param arg2 [Atomic::AtomicTerm]
|
130
|
+
# @param ctx [Core::Context] A context object
|
131
|
+
# @return [Core::Context] Updated context
|
132
|
+
def unify_atomic_terms(arg1, arg2, ctx)
|
133
|
+
arg1.eql?(arg2) ? ctx.succeeded! : ctx.failed!
|
134
|
+
end
|
135
|
+
|
136
|
+
# Unification of a composite term with an atomic term
|
137
|
+
# @param _composite [Composite::CompositeTerm]
|
138
|
+
# @param _atomic [Atomic::AtomicTerm]
|
139
|
+
# @param ctx [Core::Context] A context object
|
140
|
+
# @return [Core::Context] a failure Context
|
141
|
+
def unify_composite_atomic(_composite, _atomic, ctx)
|
142
|
+
ctx.failed!
|
143
|
+
end
|
144
|
+
|
145
|
+
# Unification of a composite term with an atomic term
|
146
|
+
# @param arg1 [Composite::CompositeTerm]
|
147
|
+
# @param arg2 [Composite::CompositeTerm]
|
148
|
+
# @param ctx [Core::Context] A context object
|
149
|
+
# @return [Core::Context] Updated context
|
150
|
+
def unify_composite_terms(arg1, arg2, ctx)
|
151
|
+
return ctx.succeeded! if arg1.null? && arg2.null?
|
152
|
+
|
153
|
+
# We do parallel iteration
|
154
|
+
visitor1 = Composite::ConsCellVisitor.df_visitor(arg1)
|
155
|
+
visitor2 = Composite::ConsCellVisitor.df_visitor(arg2)
|
156
|
+
skip_children1 = skip_children2 = false
|
157
|
+
|
158
|
+
loop do
|
159
|
+
# side.. can be: :car, :cdr, :stop
|
160
|
+
side1, cell1 = visitor1.resume(skip_children1)
|
161
|
+
side2, cell2 = visitor2.resume(skip_children2)
|
162
|
+
if side1 != side2
|
163
|
+
ctx.failed
|
164
|
+
elsif side1 == :stop
|
165
|
+
break
|
166
|
+
else
|
167
|
+
# A cell can be: nil, Atomic::AtomicTerm, Composite::ConsCell, LogVarRef
|
168
|
+
case [cell1.class, cell2.class]
|
169
|
+
when [Composite::ConsCell, Composite::ConsCell]
|
170
|
+
skip_children1 = skip_children2 = false
|
171
|
+
ctx.blackboard.succeeded!
|
172
|
+
when [Composite::ConsCell, Core::LogVarRef]
|
173
|
+
skip_children1 = true
|
174
|
+
skip_children2 = false
|
175
|
+
unification(cell1, cell2, ctx)
|
176
|
+
when [Core::LogVarRef, Composite::ConsCell]
|
177
|
+
skip_children1 = false
|
178
|
+
skip_children2 = true
|
179
|
+
do_unification(cell1, cell2, ctx)
|
180
|
+
else
|
181
|
+
skip_children1 = skip_children2 = false
|
182
|
+
unification(cell1, cell2, ctx)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
break if ctx.failure?
|
187
|
+
end
|
188
|
+
|
189
|
+
ctx
|
190
|
+
end
|
191
|
+
|
192
|
+
# Unification of a logical variable reference with an atomic term
|
193
|
+
# @param ref [Core::LogVarRef]
|
194
|
+
# @param atomic [Atomic::AtomicTerm]
|
195
|
+
# @param ctx [Core::Context] A context object
|
196
|
+
# @return [Core::Context] Updated context
|
197
|
+
def unify_ref_atomic(ref, atomic, ctx)
|
198
|
+
if ref.unbound?(ctx)
|
199
|
+
ctx.associate(ref, atomic)
|
200
|
+
ctx.succeeded!
|
201
|
+
else
|
202
|
+
assocs = ctx.associations_for(ref.name)
|
203
|
+
first_assoc = assocs.first
|
204
|
+
if first_assoc.kind_of?(Core::Association) &&
|
205
|
+
first_assoc.value.eql?(atomic)
|
206
|
+
# Trying to associate again to the same value is OK
|
207
|
+
ctx.succeeded!
|
208
|
+
else
|
209
|
+
ctx.failed!
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Unification of a logical variable and a composite
|
215
|
+
# @param ref [Core::LogVarRef]
|
216
|
+
# @param composite [Core::CompositeTerm]
|
217
|
+
# @param ctx [Core::Context] A context object
|
218
|
+
# @return [Core::Context] Updated context
|
219
|
+
def unify_ref_composite(ref, composite, ctx)
|
220
|
+
if ref.unbound?(ctx)
|
221
|
+
ctx.associate(ref, composite)
|
222
|
+
else
|
223
|
+
# Assumption: ref has only one existing association...
|
224
|
+
as = ctx.associations_for(ref.name)
|
225
|
+
first_assoc = as.first.value
|
226
|
+
unification(first_assoc, composite, ctx)
|
227
|
+
return ctx if ctx.failure?
|
228
|
+
|
229
|
+
# The association can be sometimes be redundant...
|
230
|
+
ctx.associate(ref, composite) unless first_assoc.pinned?(ctx)
|
231
|
+
end
|
232
|
+
ctx.succeeded!
|
233
|
+
ctx
|
234
|
+
end
|
235
|
+
|
236
|
+
# Unification of two logical variable references
|
237
|
+
# @param ref1 [Core::LogVarRef]
|
238
|
+
# @param ref2 [Core::LogVarRef]
|
239
|
+
# @param ctx [Core::Context] A context object
|
240
|
+
# @return [Core::Context] Updated context
|
241
|
+
def unify_references(ref1, ref2, ctx)
|
242
|
+
return ctx.succeeded! if ref1.name == ref2.name
|
243
|
+
|
244
|
+
if ref1.unbound?(ctx) || ref2.unbound?(ctx)
|
245
|
+
ctx.fuse([ref1.name, ref2.name])
|
246
|
+
ctx.succeeded!
|
247
|
+
else
|
248
|
+
raise NotImplentedError
|
249
|
+
end
|
250
|
+
# if both refs are fresh, fuse them
|
251
|
+
# if one ref is fresh & the other one isn't then bind fresh one (occurs check)
|
252
|
+
# More cases...
|
253
|
+
|
254
|
+
ctx
|
255
|
+
end
|
256
|
+
end # class
|
257
|
+
end # module
|
258
|
+
end # module
|