mini_kraken 0.1.08 → 0.1.13
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 +52 -0
- data/README.md +11 -3
- data/lib/mini_kraken/core/association_walker.rb +1 -1
- data/lib/mini_kraken/core/base_arg.rb +10 -0
- data/lib/mini_kraken/core/conde.rb +143 -0
- data/lib/mini_kraken/core/conj2.rb +5 -3
- data/lib/mini_kraken/core/def_relation.rb +49 -0
- data/lib/mini_kraken/core/disj2.rb +1 -0
- data/lib/mini_kraken/core/equals.rb +17 -13
- data/lib/mini_kraken/core/formal_arg.rb +22 -0
- data/lib/mini_kraken/core/formal_ref.rb +24 -0
- data/lib/mini_kraken/core/goal.rb +9 -3
- data/lib/mini_kraken/core/goal_arg.rb +5 -3
- data/lib/mini_kraken/core/goal_template.rb +60 -0
- data/lib/mini_kraken/core/k_boolean.rb +31 -0
- data/lib/mini_kraken/core/outcome.rb +14 -0
- data/lib/mini_kraken/core/relation.rb +7 -0
- data/lib/mini_kraken/core/variable.rb +10 -4
- data/lib/mini_kraken/core/vocabulary.rb +18 -1
- data/lib/mini_kraken/glue/fresh_env.rb +45 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +43 -26
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conde_spec.rb +147 -0
- data/spec/core/def_relation_spec.rb +96 -0
- data/spec/core/equals_spec.rb +3 -3
- data/spec/core/goal_template_spec.rb +74 -0
- data/spec/core/k_boolean_spec.rb +107 -0
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +6 -0
- data/spec/glue/fresh_env_spec.rb +27 -1
- data/spec/glue/run_star_expression_spec.rb +500 -7
- data/spec/support/factory_methods.rb +15 -0
- metadata +19 -2
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_arg'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A formal reference represents the occurrence of a formal argument name in a
|
8
|
+
# goal template argument list.
|
9
|
+
class FormalRef < BaseArg
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
def initialize(aName)
|
14
|
+
@name = validated_name(aName)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validated_name(aName)
|
20
|
+
aName
|
21
|
+
end
|
22
|
+
end # class
|
23
|
+
end # module
|
24
|
+
end # module
|
@@ -30,14 +30,20 @@ module MiniKraken
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def validated_actuals(args)
|
33
|
-
if args.size != relation.arity
|
33
|
+
if !relation.polyadic? && (args.size != relation.arity)
|
34
34
|
err_msg = "Goal has #{args.size} arguments, expected #{relation.arity}"
|
35
35
|
raise StandardError, err_msg
|
36
36
|
end
|
37
37
|
|
38
38
|
prefix = 'Invalid goal argument '
|
39
|
-
args.each do |
|
40
|
-
|
39
|
+
args.each do |actual|
|
40
|
+
if actual.kind_of?(GoalArg) || actual.kind_of?(Environment)
|
41
|
+
next
|
42
|
+
elsif actual.kind_of?(Array)
|
43
|
+
validated_actuals(actual)
|
44
|
+
else
|
45
|
+
raise StandardError, prefix + actual.to_s
|
46
|
+
end
|
41
47
|
end
|
42
48
|
|
43
49
|
args.dup
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base_arg'
|
4
|
+
|
3
5
|
module MiniKraken
|
4
6
|
module Core
|
5
|
-
# The generalization of any
|
6
|
-
# passed as arugement to a goal
|
7
|
-
class GoalArg
|
7
|
+
# The generalization of any item that can be
|
8
|
+
# passed as arugement to a goal object
|
9
|
+
class GoalArg < BaseArg
|
8
10
|
end # class
|
9
11
|
end # module
|
10
12
|
end # module
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_arg'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A meta-goal that is parametrized with generic formal arguments.
|
8
|
+
# The individual goals are instantiated when the formal arguments
|
9
|
+
# are bound to goal arguments
|
10
|
+
class GoalTemplate < BaseArg
|
11
|
+
# @return [Array<BaseArg>}] Arguments of goal template.
|
12
|
+
attr_reader :args
|
13
|
+
|
14
|
+
# @return [Relation] Main relation for the goal template
|
15
|
+
attr_reader :relation
|
16
|
+
|
17
|
+
def initialize(aRelation, theArgs)
|
18
|
+
@relation = validated_relation(aRelation)
|
19
|
+
@args = validated_args(theArgs)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param formals [Array<FormalArg>] Array of formal arguments
|
23
|
+
# @param actuals [Array<GoalArg>] Array of actual arguments
|
24
|
+
# @return [Goal] instantiate a goal object given the actuals and environment
|
25
|
+
def instantiate(formals, actuals)
|
26
|
+
formals2actuals = {}
|
27
|
+
formals.each_with_index do |frml, i|
|
28
|
+
formals2actuals[frml.name] = actuals[i]
|
29
|
+
end
|
30
|
+
|
31
|
+
do_instantiate(formals2actuals)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def validated_relation(aRelation)
|
37
|
+
aRelation
|
38
|
+
end
|
39
|
+
|
40
|
+
def validated_args(theArgs)
|
41
|
+
theArgs
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_instantiate(formals2actuals)
|
45
|
+
goal_args = []
|
46
|
+
args.each do |arg|
|
47
|
+
if arg.kind_of?(FormalRef)
|
48
|
+
goal_args << formals2actuals[arg.name]
|
49
|
+
elsif arg.kind_of?(GoalTemplate)
|
50
|
+
goal_args << arg.send(:do_instantiate, formals2actuals)
|
51
|
+
else
|
52
|
+
goal_args << arg
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Goal.new(relation, goal_args)
|
57
|
+
end
|
58
|
+
end # class
|
59
|
+
end # module
|
60
|
+
end # module
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'atomic_term'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A specialized atomic term that represents an boolean (true/false) value.
|
8
|
+
# in MiniKraken
|
9
|
+
class KBoolean < AtomicTerm
|
10
|
+
# @param aValue [Boolean, Symbol] Ruby representation of boolean value
|
11
|
+
def initialize(aValue)
|
12
|
+
super(validated_value(aValue))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validated_value(aValue)
|
18
|
+
case aValue
|
19
|
+
when true, false
|
20
|
+
aValue
|
21
|
+
when :"#t", '#t'
|
22
|
+
true
|
23
|
+
when :"#f", '#f'
|
24
|
+
false
|
25
|
+
else
|
26
|
+
raise StandardError, "Invalid boolean literal '#{aValue}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # class
|
30
|
+
end # module
|
31
|
+
end # module
|
@@ -16,6 +16,14 @@ unless MiniKraken::Core.constants(false).include? :Outcome
|
|
16
16
|
@resultant = aResult
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.failure(aParent = nil)
|
20
|
+
new(:"#u", aParent)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.success(aParent = nil)
|
24
|
+
new(:"#s", aParent)
|
25
|
+
end
|
26
|
+
|
19
27
|
def successful?
|
20
28
|
resultant == :"#s"
|
21
29
|
end
|
@@ -30,6 +38,12 @@ unless MiniKraken::Core.constants(false).include? :Outcome
|
|
30
38
|
|
31
39
|
are_equal
|
32
40
|
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def introspect
|
45
|
+
", @resultant=#{resultant}"
|
46
|
+
end
|
33
47
|
end # class
|
34
48
|
|
35
49
|
Failure = Outcome.new(:"#u")
|
@@ -16,6 +16,13 @@ module MiniKraken
|
|
16
16
|
@alt_name = alternateName
|
17
17
|
end
|
18
18
|
|
19
|
+
# A relation is polyadic when it accepts an arbitrary number of arguments.
|
20
|
+
# Most built-in relation takes a fixed number of arguments (= arity).
|
21
|
+
# @return [Boolean]
|
22
|
+
def polyadic?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
19
26
|
# Number of arguments for the relation.
|
20
27
|
# @return [Integer]
|
21
28
|
def arity
|
@@ -24,11 +24,17 @@ module MiniKraken
|
|
24
24
|
name != i_name
|
25
25
|
end
|
26
26
|
|
27
|
-
def quote(
|
28
|
-
raise StandardError, "class #{
|
27
|
+
def quote(env)
|
28
|
+
raise StandardError, "class #{env}" unless env.kind_of?(Vocabulary)
|
29
29
|
|
30
|
-
val =
|
31
|
-
|
30
|
+
val = env.quote_ref(self)
|
31
|
+
unless val
|
32
|
+
result = AnyValue.new(name, env, env.names_fused(name))
|
33
|
+
else
|
34
|
+
result = val
|
35
|
+
end
|
36
|
+
|
37
|
+
result
|
32
38
|
end
|
33
39
|
end # class
|
34
40
|
end # module
|
@@ -331,8 +331,9 @@ module MiniKraken
|
|
331
331
|
# variable name.
|
332
332
|
# @param aName [String] User-defined variable name
|
333
333
|
def names_fused(aName)
|
334
|
+
# require 'debug'
|
334
335
|
var = name2var(aName)
|
335
|
-
return [] unless var
|
336
|
+
return [] unless var&.fused?
|
336
337
|
|
337
338
|
i_name = var.i_name
|
338
339
|
names = []
|
@@ -394,6 +395,18 @@ module MiniKraken
|
|
394
395
|
name2var(aVarName) ? true : false
|
395
396
|
end
|
396
397
|
|
398
|
+
def inspect
|
399
|
+
result = +"#<#{self.class.name}:#{object_id.to_s(16)} @parent="
|
400
|
+
if parent
|
401
|
+
result << "#<#{parent.class.name}:#{parent.object_id.to_s(16)}>"
|
402
|
+
else
|
403
|
+
result << nil
|
404
|
+
end
|
405
|
+
result << introspect
|
406
|
+
result << '>'
|
407
|
+
result
|
408
|
+
end
|
409
|
+
|
397
410
|
protected
|
398
411
|
|
399
412
|
def validated_parent(aParent)
|
@@ -420,6 +433,10 @@ module MiniKraken
|
|
420
433
|
assc
|
421
434
|
end
|
422
435
|
end
|
436
|
+
|
437
|
+
def introspect
|
438
|
+
''
|
439
|
+
end
|
423
440
|
end # class
|
424
441
|
end # module
|
425
442
|
end # module
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../core/environment'
|
4
|
+
require_relative '../core/conj2'
|
4
5
|
require_relative '../core/variable'
|
5
6
|
|
6
7
|
module MiniKraken
|
7
8
|
module Glue
|
9
|
+
# A combination of an Environment (= a scope for one or more variables)
|
10
|
+
# and a goal. It quacks like a Goal object: when receiving the attain message,
|
11
|
+
# it attempt to achieve its given goal.
|
8
12
|
# (fresh (x) (== 'pea q))
|
9
13
|
# Introduces the new variable 'x'
|
10
14
|
# Takes a list of names and a goal-like object
|
@@ -13,11 +17,11 @@ module MiniKraken
|
|
13
17
|
# @return [Goal]
|
14
18
|
attr_reader :goal
|
15
19
|
|
16
|
-
# @param theNames [Array<String>]
|
17
|
-
# @param aGoal [Goal]
|
20
|
+
# @param theNames [Array<String>] The variable names
|
21
|
+
# @param aGoal [Goal, Array<Goal>] The goal to achieve or the conjunction of them.
|
18
22
|
def initialize(theNames, aGoal)
|
19
23
|
super()
|
20
|
-
@goal = aGoal
|
24
|
+
@goal = valid_goal(aGoal)
|
21
25
|
theNames.each { |nm| add_var(Core::Variable.new(nm)) }
|
22
26
|
end
|
23
27
|
|
@@ -28,6 +32,44 @@ module MiniKraken
|
|
28
32
|
self.parent = aParent
|
29
33
|
goal.attain(self)
|
30
34
|
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def introspect
|
39
|
+
+", @vars=[#{vars.keys.join(', ')}]"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def valid_goal(aGoal)
|
45
|
+
result = nil
|
46
|
+
|
47
|
+
case aGoal
|
48
|
+
when Core::Goal
|
49
|
+
result = aGoal
|
50
|
+
when FreshEnv
|
51
|
+
result = aGoal
|
52
|
+
when Array # an Array of Goal?..
|
53
|
+
goal_array = aGoal
|
54
|
+
loop do
|
55
|
+
conjunctions = []
|
56
|
+
goal_array.each_slice(2) do |uno_duo|
|
57
|
+
if uno_duo.size == 2
|
58
|
+
conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
|
59
|
+
else
|
60
|
+
conjunctions << uno_duo[0]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if conjunctions.size == 1
|
64
|
+
result = conjunctions[0]
|
65
|
+
break
|
66
|
+
end
|
67
|
+
goal_array = conjunctions
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
31
73
|
end # class
|
32
74
|
end # module
|
33
75
|
end # module
|
@@ -7,21 +7,18 @@ require_relative 'fresh_env'
|
|
7
7
|
module MiniKraken
|
8
8
|
module Glue
|
9
9
|
class RunStarExpression
|
10
|
+
# @return [FreshEnv] The environment in which run* variables will reside.
|
10
11
|
attr_reader :env
|
11
12
|
|
12
|
-
# @param
|
13
|
-
# @param goal [Core::Goal]
|
14
|
-
def initialize(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def var
|
19
|
-
env.vars.values.first
|
13
|
+
# @param var_names [String, Array<String>] One variable name or an array of names
|
14
|
+
# @param goal [Core::Goal, Array<Core::Goal>] A single goal or an array of goals to conjunct
|
15
|
+
def initialize(var_names, goal)
|
16
|
+
vnames = var_names.kind_of?(String) ? [var_names] : var_names
|
17
|
+
@env = FreshEnv.new(vnames, goal)
|
20
18
|
end
|
21
19
|
|
22
20
|
def run
|
23
|
-
result =
|
24
|
-
next_result = nil
|
21
|
+
result = []
|
25
22
|
solver = env.goal.attain(env)
|
26
23
|
# require 'debug'
|
27
24
|
loop do
|
@@ -30,24 +27,44 @@ module MiniKraken
|
|
30
27
|
outcome = solver.resume
|
31
28
|
break if outcome.nil?
|
32
29
|
|
33
|
-
if result
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
30
|
+
env.propagate(outcome) if result.empty? && outcome.successful?
|
31
|
+
result << build_solution(outcome)
|
32
|
+
end
|
33
|
+
|
34
|
+
format_solutions(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @return [Array] A vector of assignment for each variable
|
40
|
+
def build_solution(outcome)
|
41
|
+
sol = env.vars.values.map do |var|
|
42
|
+
outcome.successful? ? var.quote(outcome) : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
sol
|
46
|
+
end
|
47
|
+
|
48
|
+
# Transform the solutions into sequence of conscells.
|
49
|
+
# @param solutions [Array<Array>] An array of solution.
|
50
|
+
# A solution is in itself an array of bindings (one per variable)
|
51
|
+
def format_solutions(solutions)
|
52
|
+
solutions_as_list = solutions.map { |sol| arr2list(sol, true) }
|
53
|
+
arr2list(solutions_as_list, false)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Utility method. Transform an array into a ConsCell-based list.
|
57
|
+
# @param anArray [Array]
|
58
|
+
# @param simplify [Boolean]
|
59
|
+
def arr2list(anArray, simplify)
|
60
|
+
return anArray[0] if anArray.size == 1 && simplify
|
61
|
+
|
62
|
+
new_tail = nil
|
63
|
+
anArray.reverse_each do |elem|
|
64
|
+
new_tail = Core::ConsCell.new(elem, new_tail)
|
48
65
|
end
|
49
66
|
|
50
|
-
|
67
|
+
new_tail
|
51
68
|
end
|
52
69
|
end # class
|
53
70
|
end # module
|
data/lib/mini_kraken/version.rb
CHANGED
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper' # Use the RSpec framework
|
4
|
+
require_relative '../../lib/mini_kraken/core/k_symbol'
|
5
|
+
require_relative '../../lib/mini_kraken/core/fail'
|
6
|
+
require_relative '../../lib/mini_kraken/core/succeed'
|
7
|
+
require_relative '../../lib/mini_kraken/core/equals'
|
8
|
+
require_relative '../../lib/mini_kraken/core/environment'
|
9
|
+
require_relative '../../lib/mini_kraken/core/variable'
|
10
|
+
require_relative '../../lib/mini_kraken/core/variable_ref'
|
11
|
+
|
12
|
+
# Load the class under test
|
13
|
+
require_relative '../../lib/mini_kraken/core/conde'
|
14
|
+
|
15
|
+
module MiniKraken
|
16
|
+
module Core
|
17
|
+
describe Conde do
|
18
|
+
subject { Conde.instance }
|
19
|
+
|
20
|
+
context 'Initialization:' do
|
21
|
+
it 'should be initialized without argument' do
|
22
|
+
expect { Conde.instance }.not_to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should know its name' do
|
26
|
+
expect(subject.name).to eq('conde')
|
27
|
+
end
|
28
|
+
end # context
|
29
|
+
|
30
|
+
context 'Provided services:' do
|
31
|
+
let(:bean) { KSymbol.new(:bean) }
|
32
|
+
let(:corn) { KSymbol.new(:corn) }
|
33
|
+
let(:meal) { KSymbol.new(:meal) }
|
34
|
+
let(:oil) { KSymbol.new(:oil) }
|
35
|
+
let(:olive) { KSymbol.new(:olive) }
|
36
|
+
let(:pea) { KSymbol.new(:pea) }
|
37
|
+
let(:red) { KSymbol.new(:red) }
|
38
|
+
let(:split) { KSymbol.new(:split) }
|
39
|
+
let(:fails) { Goal.new(Fail.instance, []) }
|
40
|
+
let(:succeeds) { Goal.new(Succeed.instance, []) }
|
41
|
+
let(:var_q) { Variable.new('q') }
|
42
|
+
let(:var_x) { Variable.new('x') }
|
43
|
+
let(:var_y) { Variable.new('y') }
|
44
|
+
let(:ref_q) { VariableRef.new('q') }
|
45
|
+
let(:ref_x) { VariableRef.new('x') }
|
46
|
+
let(:ref_y) { VariableRef.new('y') }
|
47
|
+
let(:env) do
|
48
|
+
e = Environment.new
|
49
|
+
e.add_var(var_q)
|
50
|
+
e.add_var(var_x)
|
51
|
+
e.add_var(var_y)
|
52
|
+
e
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should complain when one of its argument is not a goal' do
|
56
|
+
err = StandardError
|
57
|
+
expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
|
58
|
+
expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should fails if when all goals fail' do
|
62
|
+
solver = subject.solver_for([fails, fails, fails], env)
|
63
|
+
expect(solver.resume).not_to be_successful
|
64
|
+
expect(solver.resume).to be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'yield success if first argument succeeds' do
|
68
|
+
subgoal = Goal.new(Equals.instance, [olive, ref_q])
|
69
|
+
solver = subject.solver_for([subgoal, fails, fails], env)
|
70
|
+
outcome = solver.resume
|
71
|
+
expect(outcome).to be_successful
|
72
|
+
expect(outcome.associations['q'].first.value).to eq(olive)
|
73
|
+
expect(solver.resume).to be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'yield success if second argument succeeds' do
|
77
|
+
subgoal = Goal.new(Equals.instance, [oil, ref_q])
|
78
|
+
solver = subject.solver_for([fails, subgoal, fails], env)
|
79
|
+
outcome = solver.resume
|
80
|
+
expect(outcome).to be_successful
|
81
|
+
expect(outcome.associations['q'].first.value).to eq(oil)
|
82
|
+
expect(solver.resume).to be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'yield success if third argument succeeds' do
|
86
|
+
subgoal = Goal.new(Equals.instance, [oil, ref_q])
|
87
|
+
solver = subject.solver_for([fails, fails, subgoal], env)
|
88
|
+
outcome = solver.resume
|
89
|
+
expect(outcome).to be_successful
|
90
|
+
expect(outcome.associations['q'].first.value).to eq(oil)
|
91
|
+
expect(solver.resume).to be_nil
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'yields three solutions if three goals succeed' do
|
95
|
+
# Covers frame 1:58
|
96
|
+
subgoal1 = Goal.new(Equals.instance, [olive, ref_q])
|
97
|
+
subgoal2 = Goal.new(Equals.instance, [oil, ref_q])
|
98
|
+
subgoal3 = Goal.new(Equals.instance, [pea, ref_q])
|
99
|
+
solver = subject.solver_for([subgoal1, subgoal2, subgoal3, fails], env)
|
100
|
+
|
101
|
+
# First solution
|
102
|
+
outcome1 = solver.resume
|
103
|
+
expect(outcome1).to be_successful
|
104
|
+
expect(outcome1.associations['q'].first.value).to eq(olive)
|
105
|
+
|
106
|
+
# Second solution
|
107
|
+
outcome2 = solver.resume
|
108
|
+
expect(outcome2).to be_successful
|
109
|
+
expect(outcome2.associations['q'].first.value).to eq(oil)
|
110
|
+
|
111
|
+
# Third solution
|
112
|
+
outcome3 = solver.resume
|
113
|
+
expect(outcome3).to be_successful
|
114
|
+
expect(outcome3.associations['q'].first.value).to eq(pea)
|
115
|
+
|
116
|
+
expect(solver.resume).to be_nil
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'also use conjunctions for nested goals' do
|
120
|
+
# Covers frame 1:88
|
121
|
+
subgoal1 = Goal.new(Equals.instance, [split, ref_x])
|
122
|
+
subgoal2 = Goal.new(Equals.instance, [pea, ref_y])
|
123
|
+
combo1 = [subgoal1, subgoal2]
|
124
|
+
|
125
|
+
subgoal3 = Goal.new(Equals.instance, [red, ref_x])
|
126
|
+
subgoal4 = Goal.new(Equals.instance, [bean, ref_y])
|
127
|
+
combo2 = [subgoal3, subgoal4]
|
128
|
+
solver = subject.solver_for([combo1, combo2], env)
|
129
|
+
|
130
|
+
# First solution
|
131
|
+
outcome1 = solver.resume
|
132
|
+
expect(outcome1).to be_successful
|
133
|
+
expect(outcome1.associations['x'].first.value).to eq(split)
|
134
|
+
expect(outcome1.associations['y'].first.value).to eq(pea)
|
135
|
+
|
136
|
+
# Second solution
|
137
|
+
outcome2 = solver.resume
|
138
|
+
expect(outcome2).to be_successful
|
139
|
+
expect(outcome2.associations['x'].first.value).to eq(red)
|
140
|
+
expect(outcome2.associations['y'].first.value).to eq(bean)
|
141
|
+
|
142
|
+
expect(solver.resume).to be_nil
|
143
|
+
end
|
144
|
+
end # context
|
145
|
+
end # describe
|
146
|
+
end # module
|
147
|
+
end # module
|