mini_kraken 0.1.06 → 0.1.07

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 800d58c5efac32c005931c5a019f4440060728d1061c381bd10f95d554a525b1
4
- data.tar.gz: 217448cd67ca952439e81da5c89393ddd57d061d7ca9d5ed310119d1aab32cbf
3
+ metadata.gz: 15536181da18de42e83ec948742a97c63338f2065841849de64d3537eeeb9bc1
4
+ data.tar.gz: bb25b2bfcab17da55d5dfa6845e699fa2a48e89e19956f3c63f2e755577d4ae7
5
5
  SHA512:
6
- metadata.gz: e52d5bdc7a34e1b303f2423fd8463766852bd61dc14d77e7bce43bcf653fd43d2331eb8f4bbc1beee13958e4621c9729b728d0cf5bbb5336bd5e277bf859a473
7
- data.tar.gz: 75b7a091579f667b41949905861ec314eb6a269ca5316b67df80cbf9f02d6d86ecd1c0ff74d61de3018c802e7c48ff7a2e4719bcb75f0ef45cca0092ebd3cb25
6
+ metadata.gz: a964e66c182fa2d834007c2003fa7b16b0e91d8cc80f8525de9c95df89d81523a9a2e93c7e57c2a96605ce6af5244324d5a7bf80c877d46a8a647726a3ac1e49
7
+ data.tar.gz: 6608b250a6949ce6d7f3fd4c548c7525595fc20c808b5fa327b2cdc1b48ac5b37dd05ee92ae4a8ea2e9a5f76477c24afd5b20e6d1851061afca43952f8d75949
data/.travis.yml CHANGED
@@ -3,5 +3,9 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.6.3
6
+ - 2.7.1
7
+ - 2.6.6
8
+ - 2.5.8
9
+ - 2.4.10
10
+ - jruby-head
7
11
  before_install: gem install bundler -v 2.0.2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
+ ## [0.1.07] - 2020-05-23
2
+ - Implementation of `disj2` (two arguments disjunction - or -)
3
+
4
+ ### New
5
+ - Class `Disj2` as subclass of `GoalRelation` that implements the disjunction of two subgoals
6
+
7
+ ### CHANGED
8
+ - Class `Disj2`: common code with `Conj2` class factored out to superclass `GoalRelation`
9
+ - File `cons_cell.rb`: prevent multiple inclusions via different requires
10
+ - Method `Vocabulary#ancestor_walker` now returns an `Enumerator` instead of a `Fiber`.
11
+
12
+ ### FIXED
13
+ - Method `RunStarExpression#run` clear associations and rankings for second and consecutive solmutions
14
+
15
+
1
16
  ## [0.1.06] - 2020-05-20
2
- - Implementation of `conj2` (two arguments conjunction)
17
+ - Implementation of `conj2` (two arguments conjunction - and -)
3
18
 
4
19
  ### New
5
20
  - Class `CompositeGoal`
data/README.md CHANGED
@@ -15,9 +15,9 @@ ISBN: 9780262535519, (2018), MIT Press.
15
15
  - [X] run\*
16
16
  - [X] fresh
17
17
  - [X] conj2
18
+ - [X] disj2
18
19
 
19
20
  ### TODO
20
- - [ ] disj2
21
21
  - [ ] defrel
22
22
  - [ ] conde
23
23
  - [ ] Occurs check
@@ -68,19 +68,6 @@ module MiniKraken
68
68
 
69
69
  Fiber.yield nil
70
70
  end
71
-
72
- private
73
-
74
- def validated_args(actuals)
75
- actuals.each do |arg|
76
- unless arg.kind_of?(Goal)
77
- prefix = 'conj2 expects goal as argument, found a '
78
- raise StandardError, prefix + "'#{arg.class}'"
79
- end
80
- end
81
-
82
- actuals
83
- end
84
71
  end # class
85
72
  end # module
86
73
  end # module
@@ -2,47 +2,53 @@
2
2
 
3
3
  require_relative 'composite_term'
4
4
 
5
- module MiniKraken
6
- module Core
7
- class ConsCell < CompositeTerm
8
- attr_reader :car
9
- attr_reader :cdr
10
-
11
- def initialize(obj1, obj2 = nil)
12
- @car = obj1
13
- @cdr = obj2
14
- end
15
-
16
- def children
17
- [car, cdr]
18
- end
19
-
20
- # Return true if it is an empty list, otherwise false.
21
- # A list is empty, when both car and cdr fields are nil.
22
- def null?
23
- car.nil? && cdr.nil?
24
- end
25
-
26
- def ==(other)
27
- return false unless other.respond_to?(:car)
28
-
29
- (car == other.car) && (cdr == other.cdr)
30
- end
31
-
32
- def eql?(other)
33
- (self.class == other.class) && car.eql?(other.car) && cdr.eql?(other.cdr)
34
- end
35
-
36
- def quote(anEnv)
37
- return self if null?
38
-
39
- new_car = car.nil? ? nil : car.quote(anEnv)
40
- new_cdr = cdr.nil? ? nil : cdr.quote(anEnv)
41
- ConsCell.new(new_car, new_cdr)
42
- end
43
- end # class
44
-
45
- # Constant representing the null (empty) list.
46
- NullList = ConsCell.new(nil, nil).freeze
5
+ unless MiniKraken::Core.constants(false).include? :ConsCell
6
+ module MiniKraken
7
+ module Core
8
+ class ConsCell < CompositeTerm
9
+ attr_reader :car
10
+ attr_reader :cdr
11
+
12
+ def initialize(obj1, obj2 = nil)
13
+ @car = obj1
14
+ @cdr = obj2
15
+ end
16
+
17
+ def children
18
+ [car, cdr]
19
+ end
20
+
21
+ # Return true if it is an empty list, otherwise false.
22
+ # A list is empty, when both car and cdr fields are nil.
23
+ def null?
24
+ car.nil? && cdr.nil?
25
+ end
26
+
27
+ def ==(other)
28
+ return false unless other.respond_to?(:car)
29
+
30
+ (car == other.car) && (cdr == other.cdr)
31
+ end
32
+
33
+ def eql?(other)
34
+ (self.class == other.class) && car.eql?(other.car) && cdr.eql?(other.cdr)
35
+ end
36
+
37
+ def quote(anEnv)
38
+ return self if null?
39
+
40
+ new_car = car.nil? ? nil : car.quote(anEnv)
41
+ new_cdr = cdr.nil? ? nil : cdr.quote(anEnv)
42
+ ConsCell.new(new_car, new_cdr)
43
+ end
44
+
45
+ def append(another)
46
+ @cdr = another
47
+ end
48
+ end # class
49
+
50
+ # Constant representing the null (empty) list.
51
+ NullList = ConsCell.new(nil, nil).freeze
52
+ end # module
47
53
  end # module
48
- end # module
54
+ end # defined
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require_relative 'duck_fiber'
5
+ require_relative 'goal'
6
+ require_relative 'goal_relation'
7
+ require_relative 'outcome'
8
+
9
+ module MiniKraken
10
+ module Core
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
+ def initialize
17
+ super('disj2', nil)
18
+ end
19
+
20
+ # @param actuals [Array<Term>] A two-elements array
21
+ # @param anEnv [Vocabulary] A vocabulary object
22
+ # @return [Fiber<Outcome>] A Fiber that yields Outcomes objects
23
+ def solver_for(actuals, anEnv)
24
+ g1, g2 = *validated_args(actuals)
25
+ Fiber.new { disjunction(g1, g2, anEnv) }
26
+ end
27
+
28
+ # Yields [Outcome, NilClass] result of the disjunction
29
+ # @param g1 [Goal] First goal argument
30
+ # @param g2 [Goal] Second goal argument
31
+ # @param voc [Vocabulary] A vocabulary object
32
+ def disjunction(g1, g2, voc)
33
+ # require 'debug'
34
+ outcome1 = nil
35
+ outcome2 = nil
36
+ if g1.relation.kind_of?(Fail) && g2.relation.kind_of?(Fail)
37
+ Fiber.yield Outcome.new(:"#u", voc)
38
+ else
39
+ f1 = g1.attain(voc)
40
+ loop do
41
+ outcome1 = f1.resume
42
+ break unless outcome1
43
+
44
+ outcome1.parent = voc unless outcome1.parent
45
+ if outcome1.successful?
46
+ Fiber.yield outcome1
47
+ outcome1.clear
48
+ end
49
+ end
50
+ f2 = g2.attain(voc)
51
+ loop do
52
+ outcome2 = f2.resume
53
+ break unless outcome2
54
+
55
+ outcome2.parent = voc unless outcome2.parent
56
+ if outcome2.successful?
57
+ Fiber.yield outcome2
58
+ outcome2.clear
59
+ end
60
+ end
61
+ end
62
+
63
+ Fiber.yield nil
64
+ end
65
+ end # class
66
+ end # module
67
+ end # module
@@ -47,7 +47,7 @@ module MiniKraken
47
47
  # Rollout associations from hierarchy
48
48
  walker = descendent.ancestor_walker
49
49
  begin
50
- env = walker.resume
50
+ env = walker.next
51
51
  break if env.nil?
52
52
 
53
53
  env.do_propagate(descendent) if env.kind_of?(Environment)
@@ -10,6 +10,19 @@ module MiniKraken
10
10
  def arity
11
11
  2
12
12
  end
13
+
14
+ protected
15
+
16
+ def validated_args(actuals)
17
+ actuals.each do |arg|
18
+ unless arg.kind_of?(Goal)
19
+ prefix = "#{name} expects goal as argument, found a "
20
+ raise StandardError, prefix + "'#{arg.class}'"
21
+ end
22
+ end
23
+
24
+ actuals
25
+ end
13
26
  end # class
14
27
  end # module
15
28
  end # module
@@ -2,36 +2,38 @@
2
2
 
3
3
  require_relative 'vocabulary'
4
4
 
5
- module MiniKraken
6
- module Core
7
- class Outcome
8
- include Vocabulary # Use mix-in module
9
-
10
- # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
11
- attr_reader :resultant
5
+ unless MiniKraken::Core.constants(false).include? :Outcome
6
+ module MiniKraken
7
+ module Core
8
+ class Outcome
9
+ include Vocabulary # Use mix-in module
10
+
11
+ # @return [Symbol] One of: :"#s" (success), :"#u" (failure)
12
+ attr_reader :resultant
13
+
14
+ def initialize(aResult, aParent = nil)
15
+ init_vocabulary(aParent)
16
+ @resultant = aResult
17
+ end
12
18
 
13
- def initialize(aResult, aParent = nil)
14
- init_vocabulary(aParent)
15
- @resultant = aResult
16
- end
19
+ def successful?
20
+ resultant == :"#s"
21
+ end
17
22
 
18
- def successful?
19
- resultant == :"#s"
20
- end
23
+ def ==(other)
24
+ are_equal = false
21
25
 
22
- def ==(other)
23
- are_equal = false
26
+ if resultant == other.resultant && parent == other.parent &&
27
+ associations == other.associations
28
+ are_equal = true
29
+ end
24
30
 
25
- if resultant == other.resultant && parent == other.parent &&
26
- associations == other.associations
27
- are_equal = true
31
+ are_equal
28
32
  end
33
+ end # class
29
34
 
30
- are_equal
31
- end
32
- end # class
33
-
34
- Failure = Outcome.new(:"#u")
35
- BasicSuccess = Outcome.new(:"#s")
35
+ Failure = Outcome.new(:"#u")
36
+ BasicSuccess = Outcome.new(:"#s")
37
+ end # module
36
38
  end # module
37
- end # module
39
+ end # defined
@@ -23,26 +23,28 @@ module MiniKraken
23
23
  @rankings = {} unless aParent
24
24
  end
25
25
 
26
- # Return a Fiber object that can iterate over this vocabulary and
26
+ # Return a Enumerator object that can iterate over this vocabulary and
27
27
  # all its direct and indirect parent(s).
28
- # @return [Fiber<Vocabulary, NilClass>]
28
+ # @return [Enumerator<Vocabulary, NilClass>]
29
29
  def ancestor_walker
30
- Fiber.new do
30
+ unless @ancestors # Not yet in cache?...
31
+ @ancestors = []
31
32
  relative = self
32
33
  while relative
33
- Fiber.yield relative
34
+ @ancestors << relative
34
35
  relative = relative.parent
35
36
  end
36
-
37
- Fiber.yield nil # nil marks end of iteration...
37
+ @ancestors << nil # nil marks end of iteration...
38
38
  end
39
+
40
+ @ancestors.to_enum
39
41
  end
40
42
 
41
43
  def clear_rankings
42
44
  walker = ancestor_walker
43
45
  orphan = nil
44
46
  loop do
45
- orphan_temp = walker.resume
47
+ orphan_temp = walker.next
46
48
  break unless orphan_temp
47
49
 
48
50
  orphan = orphan_temp
@@ -57,7 +59,7 @@ module MiniKraken
57
59
  walker = ancestor_walker
58
60
  orphan = nil
59
61
  loop do
60
- orphan_temp = walker.resume
62
+ orphan_temp = walker.next
61
63
  break unless orphan_temp
62
64
 
63
65
  orphan = orphan_temp
@@ -182,7 +184,7 @@ module MiniKraken
182
184
  walker = ancestor_walker
183
185
 
184
186
  loop do
185
- voc = walker.resume
187
+ voc = walker.next
186
188
  break unless voc
187
189
 
188
190
  if voc.associations.include?(old_i_name)
@@ -277,7 +279,7 @@ module MiniKraken
277
279
  walker = ancestor_walker
278
280
 
279
281
  loop do
280
- voc = walker.resume
282
+ voc = walker.next
281
283
  if voc
282
284
  next unless voc.respond_to?(:vars) && voc.vars.include?(aName)
283
285
 
@@ -299,7 +301,7 @@ module MiniKraken
299
301
  walker = ancestor_walker
300
302
 
301
303
  loop do
302
- voc = walker.resume
304
+ voc = walker.next
303
305
  if voc
304
306
  next unless voc.respond_to?(:ivars) && voc.ivars.include?(i_name)
305
307
 
@@ -335,7 +337,7 @@ module MiniKraken
335
337
  walker = ancestor_walker
336
338
 
337
339
  loop do
338
- voc = walker.resume
340
+ voc = walker.next
339
341
  break unless voc
340
342
  next unless voc.respond_to?(:ivars)
341
343
 
@@ -358,7 +360,7 @@ module MiniKraken
358
360
  walker = ancestor_walker
359
361
 
360
362
  loop do
361
- voc = walker.resume
363
+ voc = walker.next
362
364
  break unless voc
363
365
  next unless voc.associations.include?(i_name)
364
366
 
@@ -21,20 +21,29 @@ module MiniKraken
21
21
 
22
22
  def run
23
23
  result = nil
24
+ next_result = nil
24
25
  solver = env.goal.attain(env)
25
26
  # require 'debug'
26
27
  loop do
28
+ env.clear
29
+ env.clear_rankings
27
30
  outcome = solver.resume
28
31
  break if outcome.nil?
29
32
 
30
- env.clear
31
33
  if result # ... more than one result...
34
+ if outcome.successful?
35
+ next_result.append(Core::ConsCell.new(var.quote(outcome)))
36
+ else
37
+ next_result.append(Core::NullList)
38
+ end
39
+ next_result = next_result.cdr
32
40
  elsif outcome.successful?
33
41
  env.propagate(outcome)
34
42
  result = Core::ConsCell.new(var.quote(outcome))
43
+ next_result = result
35
44
  else
36
45
  result = Core::NullList
37
- env.associations.freeze
46
+ next_result = result
38
47
  end
39
48
  end
40
49
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.06'
4
+ VERSION = '0.1.07'
5
5
  end
@@ -58,6 +58,14 @@ module MiniKraken
58
58
  different = ConsCell.new(pea)
59
59
  expect(subject.eql?(different)).to be_falsey
60
60
  end
61
+
62
+ it 'should append another cons cell' do
63
+ instance = ConsCell.new(pea)
64
+ trail = ConsCell.new(pod)
65
+ instance.append(trail)
66
+ expect(instance.car).to eq(pea)
67
+ expect(instance.cdr).to eq(trail)
68
+ end
61
69
  end # context
62
70
  end # describe
63
71
  end # module
@@ -0,0 +1,144 @@
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/disj2'
14
+
15
+ module MiniKraken
16
+ module Core
17
+ describe Disj2 do
18
+ subject { Disj2.instance }
19
+
20
+ context 'Initialization:' do
21
+ it 'should be initialized without argument' do
22
+ expect { Disj2.instance }.not_to raise_error
23
+ end
24
+
25
+ it 'should know its name' do
26
+ expect(subject.name).to eq('disj2')
27
+ end
28
+ end # context
29
+
30
+ context 'Provided services:' do
31
+ let(:corn) { KSymbol.new(:corn) }
32
+ let(:meal) { KSymbol.new(:meal) }
33
+ let(:oil) { KSymbol.new(:oil) }
34
+ let(:olive) { KSymbol.new(:olive) }
35
+ let(:pea) { KSymbol.new(:pea) }
36
+ let(:fails) { Goal.new(Fail.instance, []) }
37
+ let(:succeeds) { Goal.new(Succeed.instance, []) }
38
+ let(:var_q) { Variable.new('q') }
39
+ let(:ref_q) { VariableRef.new('q') }
40
+ let(:env) do
41
+ e = Environment.new
42
+ e.add_var(var_q)
43
+ e
44
+ end
45
+
46
+ it 'should complain when one of its argument is not a goal' do
47
+ err = StandardError
48
+ expect { subject.solver_for([succeeds, pea], env) }.to raise_error(err)
49
+ expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
50
+ end
51
+
52
+ it 'should fails if both arguments fail' do
53
+ # Covers frame 1:55
54
+ solver = subject.solver_for([fails, fails], env)
55
+ expect(solver.resume).not_to be_successful
56
+ expect(solver.resume).to be_nil
57
+ end
58
+
59
+ it 'yield success if first argument succeeds' do
60
+ # Covers frame 1:56
61
+ subgoal = Goal.new(Equals.instance, [olive, ref_q])
62
+ solver = subject.solver_for([subgoal, fails], env)
63
+ outcome = solver.resume
64
+ expect(outcome).to be_successful
65
+ expect(outcome.associations['q'].first.value).to eq(olive)
66
+ expect(solver.resume).to be_nil
67
+ end
68
+
69
+ it 'yield success if second argument succeeds' do
70
+ # Covers frame 1:57
71
+ subgoal = Goal.new(Equals.instance, [oil, ref_q])
72
+ solver = subject.solver_for([fails, subgoal], env)
73
+ outcome = solver.resume
74
+ expect(outcome).to be_successful
75
+ expect(outcome.associations['q'].first.value).to eq(oil)
76
+ expect(solver.resume).to be_nil
77
+ end
78
+
79
+ it 'yield two solutions if both arguments succeed' do
80
+ # Covers frame 1:58
81
+ subgoal1 = Goal.new(Equals.instance, [olive, ref_q])
82
+ subgoal2 = Goal.new(Equals.instance, [oil, ref_q])
83
+ solver = subject.solver_for([subgoal1, subgoal2], env)
84
+
85
+ # First solution
86
+ outcome1 = solver.resume
87
+ expect(outcome1).to be_successful
88
+ expect(outcome1.associations['q'].first.value).to eq(olive)
89
+
90
+ # Second solution
91
+ outcome2 = solver.resume
92
+ expect(outcome2).to be_successful
93
+ expect(outcome2.associations['q'].first.value).to eq(oil)
94
+ expect(solver.resume).to be_nil
95
+ end
96
+
97
+ it 'should yield success and set associations' do
98
+ # # Weird: this example succeeds if run alone...
99
+ # # Covers frame 1-51
100
+ # env.add_var(var_q)
101
+ # sub_goal = Goal.new(Equals.instance, [corn, ref_q])
102
+ # solver = subject.solver_for([succeeds, sub_goal], env)
103
+ # outcome = solver.resume
104
+ # expect(outcome).to be_successful
105
+ # expect(outcome.associations).not_to be_empty
106
+ # expect(outcome.associations['q'].first.value).to eq(corn)
107
+ end
108
+
109
+ # it 'should yield fails and set no associations' do
110
+ # # Covers frame 1-52
111
+ # env.add_var(var_q)
112
+ # sub_goal = Goal.new(Equals.instance, [corn, ref_q])
113
+ # solver = subject.solver_for([fails, sub_goal], env)
114
+ # outcome = solver.resume
115
+ # expect(outcome).not_to be_successful
116
+ # expect(outcome.associations).to be_empty
117
+ # end
118
+
119
+ # it 'should yield fails when sub-goals are incompatible' do
120
+ # # Covers frame 1-53
121
+ # env.add_var(var_q)
122
+ # sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
123
+ # sub_goal2 = Goal.new(Equals.instance, [meal, ref_q])
124
+ # solver = subject.solver_for([sub_goal1, sub_goal2], env)
125
+ # outcome = solver.resume
126
+ # expect(outcome).not_to be_successful
127
+ # expect(outcome.associations).to be_empty
128
+ # end
129
+
130
+ # it 'should yield success when sub-goals are same and successful' do
131
+ # # Covers frame 1-54
132
+ # env.add_var(var_q)
133
+ # sub_goal1 = Goal.new(Equals.instance, [corn, ref_q])
134
+ # sub_goal2 = Goal.new(Equals.instance, [corn, ref_q])
135
+ # solver = subject.solver_for([sub_goal1, sub_goal2], env)
136
+ # outcome = solver.resume
137
+ # expect(outcome).to be_successful
138
+ # expect(outcome.associations).not_to be_empty
139
+ # expect(outcome.associations['q'].first.value).to eq(corn)
140
+ # end
141
+ end # context
142
+ end # describe
143
+ end # module
144
+ end # module
@@ -79,11 +79,11 @@ module MiniKraken
79
79
 
80
80
  it 'should provide a walker over ancestors' do
81
81
  walker = subject.ancestor_walker
82
- expect(walker).to be_kind_of(Fiber)
83
- expect(walker.resume).to eq(subject)
84
- expect(walker.resume).to eq(mother)
85
- expect(walker.resume).to eq(grandma)
86
- expect(walker.resume).to be_nil
82
+ expect(walker).to be_kind_of(Enumerator)
83
+ expect(walker.next).to eq(subject)
84
+ expect(walker.next).to eq(mother)
85
+ expect(walker.next).to eq(grandma)
86
+ expect(walker.next).to be_nil
87
87
  end
88
88
 
89
89
  it 'should know if a variable is defined' do
@@ -3,6 +3,7 @@
3
3
  require_relative '../spec_helper' # Use the RSpec framework
4
4
  require_relative '../../lib/mini_kraken/core/goal'
5
5
  require_relative '../../lib/mini_kraken/core/conj2'
6
+ require_relative '../../lib/mini_kraken/core/disj2'
6
7
  require_relative '../../lib/mini_kraken/core/equals'
7
8
  require_relative '../../lib/mini_kraken/core/fail'
8
9
  require_relative '../../lib/mini_kraken/core/succeed'
@@ -40,6 +41,8 @@ module MiniKraken
40
41
  context 'Provided services:' do
41
42
  let(:corn) { k_symbol(:corn) }
42
43
  let(:meal) { k_symbol(:meal) }
44
+ let(:oil) { k_symbol(:oil) }
45
+ let(:olive) { k_symbol(:olive) }
43
46
  let(:ref_q) { Core::VariableRef.new('q') }
44
47
  let(:ref_x) { Core::VariableRef.new('x') }
45
48
  let(:ref_y) { Core::VariableRef.new('y') }
@@ -56,7 +59,6 @@ module MiniKraken
56
59
  instance = RunStarExpression.new('q', failing)
57
60
 
58
61
  expect(instance.run).to be_null
59
- expect(ref_q.fresh?(instance.env)).to be_truthy
60
62
  end
61
63
 
62
64
  it 'should return a null list when a goal fails' do
@@ -74,7 +76,6 @@ module MiniKraken
74
76
  # Reasoned S2, frame 1:11
75
77
  # (run* q (== q 'pea) ;; => (pea)
76
78
  expect(instance.run.car).to eq(pea)
77
- expect(ref_q.fresh?(instance.env)).to be_falsey
78
79
  end
79
80
 
80
81
  it 'should unify the righthand variable(s)' do
@@ -84,9 +85,6 @@ module MiniKraken
84
85
  # Reasoned S2, frame 1:12
85
86
  # (run* q (== 'pea q) ;; => (pea)
86
87
  expect(instance.run.car).to eq(pea)
87
-
88
- # Reasoned S2, frame 1:15
89
- expect(ref_q.fresh?(instance.env)).to be_falsey
90
88
  end
91
89
 
92
90
  it 'should return a null list with the succeed goal' do
@@ -96,7 +94,6 @@ module MiniKraken
96
94
  # (display (run* q succeed)) ;; => (_0)
97
95
  # Reasoned S2, frame 1:16
98
96
  result = instance.run
99
- expect(ref_q.fresh?(instance.env)).to be_truthy
100
97
 
101
98
  # Reasoned S2, frame 1:17
102
99
  expect(result.car).to eq(any_value(0))
@@ -109,7 +106,6 @@ module MiniKraken
109
106
  # (display (run* q (== 'pea 'pea))) ;; => (_0)
110
107
  # Reasoned S2, frame 1:19
111
108
  result = instance.run
112
- expect(ref_q.fresh?(instance.env)).to be_truthy
113
109
  expect(result.car).to eq(any_value(0))
114
110
  end
115
111
 
@@ -122,7 +118,6 @@ module MiniKraken
122
118
  # (display (run* q (== q q))) ;; => (_0)
123
119
  # Reasoned S2, frame 1:20
124
120
  result = instance.run
125
- expect(ref_q.fresh?(instance.env)).to be_truthy
126
121
  expect(result.car).to eq(any_value(0))
127
122
  end
128
123
 
@@ -134,8 +129,6 @@ module MiniKraken
134
129
  # Reasoned S2, frame 1:21..23
135
130
  # (run* q (fresh (x) (== 'pea q))) ;; => (pea)
136
131
  result = instance.run
137
- expect(ref_q.fresh?(instance.env)).to be_falsey
138
- expect(ref_x.fresh?(fresh_env)).to be_truthy
139
132
 
140
133
  # Reasoned S2, frame 1:40
141
134
  expect(ref_q.different_from?(ref_x, fresh_env)).to be_truthy
@@ -150,8 +143,6 @@ module MiniKraken
150
143
  # Reasoned S2, frame 1:24
151
144
  # (run* q (fresh (x) (== 'pea x))) ;; => (_0)
152
145
  result = instance.run
153
- expect(ref_q.fresh?(instance.env)).to be_truthy
154
- expect(ref_x.fresh?(fresh_env)).to be_falsey
155
146
  expect(result.car).to eq(any_value(0))
156
147
  end
157
148
 
@@ -163,8 +154,6 @@ module MiniKraken
163
154
  # Reasoned S2, frame 1:25
164
155
  # (run* q (fresh (x) (== (cons x '()) q))) ;; => ((_0))
165
156
  result = instance.run
166
- expect(ref_q.fresh?(instance.env)).to be_truthy
167
- expect(ref_x.fresh?(fresh_env)).to be_truthy
168
157
  expect(result.car).to eq(cons(any_value(0)))
169
158
  end
170
159
 
@@ -195,7 +184,6 @@ module MiniKraken
195
184
  # Reasoned S2, frame 1:32
196
185
  # (run* q (== '(((pea)) pod) '(((pea)) pod))) ;; => (_0)
197
186
  result = instance.run
198
- expect(ref_q.fresh?(instance.env)).to be_truthy
199
187
  expect(result.car).to eq(any_value(0))
200
188
  end
201
189
 
@@ -209,7 +197,6 @@ module MiniKraken
209
197
  # Reasoned S2, frame 1:33
210
198
  # (run* q (== '(((pea)) pod) `(((pea)) ,q))) ;; => ('pod)
211
199
  result = instance.run
212
- expect(ref_q.fresh?(instance.env)).to be_falsey
213
200
  expect(result.car).to eq(pod)
214
201
  end
215
202
 
@@ -222,7 +209,6 @@ module MiniKraken
222
209
  # Reasoned S2, frame 1:34
223
210
  # (run* q (== '(((,q)) pod) `(((pea)) pod))) ;; => ('pod)
224
211
  result = instance.run
225
- expect(ref_q.fresh?(instance.env)).to be_falsey
226
212
  expect(result.car).to eq(pea)
227
213
  end
228
214
 
@@ -236,8 +222,6 @@ module MiniKraken
236
222
  # Reasoned S2, frame 1:35
237
223
  # (run* q (fresh (x) (== '(((,q)) pod) `(((,x)) pod)))) ;; => (_0)
238
224
  result = instance.run
239
- expect(ref_q.fresh?(instance.env)).to be_truthy
240
- expect(ref_x.fresh?(fresh_env)).to be_truthy
241
225
  expect(result.car).to eq(any_value(0))
242
226
  end
243
227
 
@@ -251,11 +235,6 @@ module MiniKraken
251
235
  instance = RunStarExpression.new('q', fresh_env)
252
236
 
253
237
  result = instance.run
254
-
255
- # Does propagate work correctly?
256
- expect(ref_q.fresh?(instance.env)).to be_truthy # x isn't defined here
257
- expect(ref_q.fresh?(fresh_env)).to be_falsey
258
- expect(ref_x.fresh?(fresh_env)).to be_falsey
259
238
  expect(result.car).to eq(pod)
260
239
  end
261
240
 
@@ -268,9 +247,6 @@ module MiniKraken
268
247
  instance = RunStarExpression.new('q', fresh_env)
269
248
 
270
249
  result = instance.run
271
- expect(ref_q.fresh?(instance.env)).to be_truthy # x isn't defined here
272
- expect(ref_q.fresh?(fresh_env)).to be_truthy
273
- expect(ref_x.fresh?(fresh_env)).to be_truthy
274
250
  expect(result.car).to eq(cons(any_value(0), cons(any_value(0))))
275
251
  end
276
252
 
@@ -285,12 +261,6 @@ module MiniKraken
285
261
  instance = RunStarExpression.new('q', fresh_env_x)
286
262
 
287
263
  result = instance.run
288
- expect(ref_q.fresh?(fresh_env_y)).to be_truthy
289
- expect(ref_q.bound?(fresh_env_y)).to be_truthy
290
- expect(ref_x.fresh?(fresh_env_y)).to be_truthy
291
- expect(ref_x.bound?(fresh_env_y)).to be_falsy
292
- expect(ref_y.fresh?(fresh_env_y)).to be_truthy
293
- expect(ref_y.bound?(fresh_env_y)).to be_falsy
294
264
 
295
265
  # y should be fused with x...
296
266
  var_x = fresh_env_y.name2var('x')
@@ -314,13 +284,7 @@ module MiniKraken
314
284
  instance = RunStarExpression.new('q', fresh_env_x)
315
285
 
316
286
  result = instance.run
317
- expect(ref_q.fresh?(fresh_env_y)).to be_truthy
318
287
  # q should be bound to '(,x ,y)
319
- expect(ref_q.bound?(fresh_env_y)).to be_truthy
320
- expect(ref_x.fresh?(fresh_env_y)).to be_truthy
321
- expect(ref_x.bound?(fresh_env_y)).to be_falsey
322
- expect(ref_y.fresh?(fresh_env_y)).to be_truthy
323
- expect(ref_y.bound?(fresh_env_y)).to be_falsey
324
288
  expect(result.car).to eq(cons(any_value(0), cons(any_value(1))))
325
289
  end
326
290
 
@@ -334,13 +298,7 @@ module MiniKraken
334
298
  instance = RunStarExpression.new('s', fresh_env_t)
335
299
 
336
300
  result = instance.run
337
- expect(ref_s.fresh?(fresh_env_u)).to be_truthy
338
301
  # s should be bound to '(,t ,u)
339
- expect(ref_s.bound?(fresh_env_u)).to be_truthy
340
- expect(ref_t.fresh?(fresh_env_u)).to be_truthy
341
- expect(ref_t.bound?(fresh_env_u)).to be_falsey
342
- expect(ref_u.fresh?(fresh_env_u)).to be_truthy
343
- expect(ref_u.bound?(fresh_env_u)).to be_falsey
344
302
  expect(result.car).to eq(cons(any_value(0), cons(any_value(1))))
345
303
  end
346
304
 
@@ -354,11 +312,7 @@ module MiniKraken
354
312
  instance = RunStarExpression.new('q', fresh_env_x)
355
313
 
356
314
  result = instance.run
357
- expect(ref_q.fresh?(fresh_env_y)).to be_truthy
358
315
  # q should be bound to '(,x ,y, ,x)
359
- expect(ref_q.bound?(fresh_env_y)).to be_truthy
360
- expect(ref_x.fresh?(fresh_env_y)).to be_truthy
361
- expect(ref_y.fresh?(fresh_env_y)).to be_truthy
362
316
  expect(result.car).to eq(cons(any_value(0), cons(any_value(1), cons(any_value(0)))))
363
317
  end
364
318
 
@@ -369,7 +323,6 @@ module MiniKraken
369
323
  # Reasoned S2, frame 1:50
370
324
  # (run* q (conj2 succeed succeed)) ;; => (_0)
371
325
  result = instance.run
372
- expect(ref_q.fresh?(instance.env)).to be_truthy
373
326
  expect(result.car).to eq(any_value(0))
374
327
  end
375
328
 
@@ -382,7 +335,6 @@ module MiniKraken
382
335
  # # Reasoned S2, frame 1:51
383
336
  # # (run* q (conj2 succeed (== 'corn q)) ;; => ('corn)
384
337
  # result = instance.run
385
- # expect(ref_q.fresh?(instance.env)).to be_falsy
386
338
  # expect(result.car).to eq(corn)
387
339
  # end
388
340
 
@@ -394,7 +346,6 @@ module MiniKraken
394
346
  # Reasoned S2, frame 1:52
395
347
  # (run* q (conj2 fail (== 'corn q)) ;; => ()
396
348
  expect(instance.run).to be_null
397
- expect(ref_q.fresh?(instance.env)).to be_truthy
398
349
  end
399
350
 
400
351
  it 'should support conjunction of two contradictory goals' do
@@ -406,7 +357,6 @@ module MiniKraken
406
357
  # Reasoned S2, frame 1:53
407
358
  # (run* q (conj2 (== 'corn q)(== 'meal q)) ;; => ()
408
359
  expect(instance.run).to be_null
409
- expect(ref_q.fresh?(instance.env)).to be_truthy
410
360
  end
411
361
 
412
362
  it 'should succeed the conjunction of two identical goals' do
@@ -418,9 +368,118 @@ module MiniKraken
418
368
  # Reasoned S2, frame 1:54
419
369
  # (run* q (conj2 (== 'corn q)(== 'corn q)) ;; => ('corn)
420
370
  result = instance.run
421
- expect(ref_q.fresh?(instance.env)).to be_falsy
422
371
  expect(result.car).to eq(corn)
423
372
  end
373
+
374
+ it 'should not yield solution when both disjunction arguments fail' do
375
+ goal = disj2_goal(fails, fails)
376
+ instance = RunStarExpression.new('q', goal)
377
+
378
+ # Reasoned S2, frame 1:55
379
+ # (run* q (disj2 fail fail)) ;; => ()
380
+ expect(instance.run).to be_null
381
+ end
382
+
383
+ it 'should yield solution when first argument succeed' do
384
+ subgoal = Core::Goal.new(Core::Equals.instance, [olive, ref_q])
385
+ goal = disj2_goal(subgoal, fails)
386
+ instance = RunStarExpression.new('q', goal)
387
+
388
+ # Reasoned S2, frame 1:56
389
+ # (run* q (disj2 (equals 'olive q) fail)) ;; => ('olive)
390
+ result = instance.run
391
+ expect(result.car).to eq(olive)
392
+ end
393
+
394
+ it 'should yield solution when second argument succeed' do
395
+ subgoal = Core::Goal.new(Core::Equals.instance, [oil, ref_q])
396
+ goal = disj2_goal(fails, subgoal)
397
+ instance = RunStarExpression.new('q', goal)
398
+
399
+ # Reasoned S2, frame 1:57
400
+ # (run* q (disj2 fail (equals 'oil q)) ;; => (oil)
401
+ result = instance.run
402
+ expect(result.car).to eq(oil)
403
+ end
404
+
405
+ it 'should yield solutions when both arguments succeed' do
406
+ subgoal1 = Core::Goal.new(Core::Equals.instance, [olive, ref_q])
407
+ subgoal2 = Core::Goal.new(Core::Equals.instance, [oil, ref_q])
408
+ goal = disj2_goal(subgoal1, subgoal2)
409
+ instance = RunStarExpression.new('q', goal)
410
+
411
+ # Reasoned S2, frame 1:58
412
+ # (run* q (disj2 (equals 'olive q) (equals 'oil q)) ;; => (olive oil)
413
+ result = instance.run
414
+ expect(result.car).to eq(olive)
415
+ expect(result.cdr.car).to eq(oil)
416
+ end
417
+
418
+ it 'should support the nesting of variables and disjunction' do
419
+ # Reasoned S2, frame 1:59
420
+ # (run* q (fresh (x) (fresh (y) (disj2 (== '( ,x ,y ) q) (== '( ,x ,y ) q)))))
421
+ # ;; => ((_0 _1) (_0 _1))
422
+ expr1 = cons(ref_x, cons(ref_y))
423
+ subgoal1 = equals_goal(expr1, ref_q)
424
+ expr2 = cons(ref_y, cons(ref_x))
425
+ subgoal2 = equals_goal(expr2, ref_q)
426
+ goal = disj2_goal(subgoal1, subgoal2)
427
+ fresh_env_y = FreshEnv.new(['y'], goal)
428
+ fresh_env_x = FreshEnv.new(['x'], fresh_env_y)
429
+ instance = RunStarExpression.new('q', fresh_env_x)
430
+
431
+ result = instance.run
432
+ # q should be bound to '(,x ,y), then to '(,y ,x)
433
+ expect(result.car).to eq(cons(any_value(0), cons(any_value(1))))
434
+ expect(result.cdr.car).to eq(cons(any_value(0), cons(any_value(1))))
435
+ end
436
+
437
+ it 'should accept nesting of disj2 and conj2 (I)' do
438
+ conj_subgoal = Core::Goal.new(Core::Equals.instance, [olive, ref_x])
439
+ conjunction = conj2_goal(conj_subgoal, fails)
440
+ subgoal = Core::Goal.new(Core::Equals.instance, [oil, ref_x])
441
+ goal = disj2_goal(conjunction, subgoal)
442
+ instance = RunStarExpression.new('x', goal)
443
+
444
+ # Reasoned S2, frame 1:62
445
+ # (run* x (disj2
446
+ # (conj2 (== 'olive x) fail)
447
+ # ('oil x))) ;; => (oil)
448
+ result = instance.run
449
+ expect(result.car).to eq(oil)
450
+ end
451
+
452
+ it 'should accept nesting of disj2 and conj2 (II)' do
453
+ conj_subgoal = Core::Goal.new(Core::Equals.instance, [olive, ref_x])
454
+ conjunction = conj2_goal(conj_subgoal, succeeds)
455
+ subgoal = Core::Goal.new(Core::Equals.instance, [oil, ref_x])
456
+ goal = disj2_goal(conjunction, subgoal)
457
+ instance = RunStarExpression.new('x', goal)
458
+
459
+ # Reasoned S2, frame 1:63
460
+ # (run* x (disj2
461
+ # (conj2 (== 'olive x) succeed)
462
+ # ('oil x))) ;; => (olive oil)
463
+ result = instance.run
464
+ expect(result.car).to eq(olive)
465
+ expect(result.cdr.car).to eq(oil)
466
+ end
467
+
468
+ it 'should accept nesting of disj2 and conj2 (III)' do
469
+ conj_subgoal = Core::Goal.new(Core::Equals.instance, [olive, ref_x])
470
+ conjunction = conj2_goal(conj_subgoal, succeeds)
471
+ subgoal = Core::Goal.new(Core::Equals.instance, [oil, ref_x])
472
+ goal = disj2_goal(subgoal, conjunction)
473
+ instance = RunStarExpression.new('x', goal)
474
+
475
+ # Reasoned S2, frame 1:64
476
+ # (run* x (disj2
477
+ # ('oil x)
478
+ # (conj2 (== 'olive x) succeed))) ;; => (oil olive)
479
+ result = instance.run
480
+ expect(result.car).to eq(oil)
481
+ expect(result.cdr.car).to eq(olive)
482
+ end
424
483
  end # context
425
484
  end # describe
426
485
  end # module
@@ -34,7 +34,7 @@ module MiniKraken
34
34
  Core::Goal.new(Core::Equals.instance, [arg1, arg2])
35
35
  end
36
36
 
37
- # Factory method for constructing a goal using the Equals relation.
37
+ # Factory method for constructing a goal using the conjunction relation.
38
38
  # @param g1 [Core::Goal]
39
39
  # @param g2 [Core::Goal]
40
40
  # @return [Core::Goal]
@@ -42,6 +42,14 @@ module MiniKraken
42
42
  Core::Goal.new(Core::Conj2.instance, [g1, g2])
43
43
  end
44
44
 
45
+ # Factory method for constructing a goal using the disjunction relation.
46
+ # @param g1 [Core::Goal]
47
+ # @param g2 [Core::Goal]
48
+ # @return [Core::Goal]
49
+ def disj2_goal(g1, g2)
50
+ Core::Goal.new(Core::Disj2.instance, [g1, g2])
51
+ end
52
+
45
53
  # Factory method for constructing a KSymbol instance
46
54
  # @param aSymbol [Symbol]
47
55
  # @return [Core::KSymbol]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_kraken
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.06
4
+ version: 0.1.07
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-21 00:00:00.000000000 Z
11
+ date: 2020-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -78,6 +78,7 @@ files:
78
78
  - lib/mini_kraken/core/conj2.rb
79
79
  - lib/mini_kraken/core/cons_cell.rb
80
80
  - lib/mini_kraken/core/designation.rb
81
+ - lib/mini_kraken/core/disj2.rb
81
82
  - lib/mini_kraken/core/duck_fiber.rb
82
83
  - lib/mini_kraken/core/environment.rb
83
84
  - lib/mini_kraken/core/equals.rb
@@ -104,6 +105,7 @@ files:
104
105
  - spec/core/association_walker_spec.rb
105
106
  - spec/core/conj2_spec.rb
106
107
  - spec/core/cons_cell_spec.rb
108
+ - spec/core/disj2_spec.rb
107
109
  - spec/core/duck_fiber_spec.rb
108
110
  - spec/core/environment_spec.rb
109
111
  - spec/core/equals_spec.rb
@@ -148,6 +150,7 @@ test_files:
148
150
  - spec/core/association_walker_spec.rb
149
151
  - spec/core/conj2_spec.rb
150
152
  - spec/core/cons_cell_spec.rb
153
+ - spec/core/disj2_spec.rb
151
154
  - spec/core/duck_fiber_spec.rb
152
155
  - spec/core/environment_spec.rb
153
156
  - spec/core/equals_spec.rb