mini_kraken 0.2.04 → 0.3.00
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 +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
|