mini_kraken 0.1.12 → 0.2.03

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +334 -0
  3. data/CHANGELOG.md +54 -0
  4. data/README.md +95 -13
  5. data/lib/mini_kraken.rb +7 -1
  6. data/lib/mini_kraken/core/any_value.rb +5 -1
  7. data/lib/mini_kraken/core/atomic_term.rb +1 -0
  8. data/lib/mini_kraken/core/conde.rb +1 -1
  9. data/lib/mini_kraken/core/conj2.rb +3 -3
  10. data/lib/mini_kraken/core/cons_cell.rb +29 -1
  11. data/lib/mini_kraken/core/cons_cell_visitor.rb +102 -0
  12. data/lib/mini_kraken/core/def_relation.rb +4 -0
  13. data/lib/mini_kraken/core/disj2.rb +2 -2
  14. data/lib/mini_kraken/core/environment.rb +2 -2
  15. data/lib/mini_kraken/core/equals.rb +60 -26
  16. data/lib/mini_kraken/core/formal_ref.rb +2 -1
  17. data/lib/mini_kraken/core/goal.rb +4 -2
  18. data/lib/mini_kraken/core/goal_template.rb +44 -2
  19. data/lib/mini_kraken/core/k_boolean.rb +4 -0
  20. data/lib/mini_kraken/core/k_symbol.rb +11 -0
  21. data/lib/mini_kraken/core/outcome.rb +11 -1
  22. data/lib/mini_kraken/core/variable.rb +10 -4
  23. data/lib/mini_kraken/core/variable_ref.rb +7 -0
  24. data/lib/mini_kraken/core/vocabulary.rb +8 -3
  25. data/lib/mini_kraken/glue/dsl.rb +236 -0
  26. data/lib/mini_kraken/glue/fresh_env.rb +31 -3
  27. data/lib/mini_kraken/glue/fresh_env_factory.rb +83 -0
  28. data/lib/mini_kraken/glue/run_star_expression.rb +3 -5
  29. data/lib/mini_kraken/version.rb +1 -1
  30. data/mini_kraken.gemspec +6 -3
  31. data/spec/.rubocop.yml +13 -0
  32. data/spec/core/conde_spec.rb +10 -10
  33. data/spec/core/conj2_spec.rb +7 -7
  34. data/spec/core/cons_cell_spec.rb +35 -0
  35. data/spec/core/cons_cell_visitor_spec.rb +144 -0
  36. data/spec/core/def_relation_spec.rb +6 -5
  37. data/spec/core/disj2_spec.rb +5 -5
  38. data/spec/core/duck_fiber_spec.rb +2 -2
  39. data/spec/core/equals_spec.rb +34 -21
  40. data/spec/core/goal_spec.rb +2 -2
  41. data/spec/core/k_boolean_spec.rb +6 -0
  42. data/spec/core/k_symbol_spec.rb +4 -0
  43. data/spec/core/outcome_spec.rb +8 -0
  44. data/spec/core/variable_ref_spec.rb +3 -0
  45. data/spec/glue/dsl_chap1_spec.rb +679 -0
  46. data/spec/glue/dsl_chap2_spec.rb +100 -0
  47. data/spec/glue/fresh_env_factory_spec.rb +97 -0
  48. data/spec/glue/run_star_expression_spec.rb +11 -11
  49. metadata +17 -4
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/environment'
4
+ require_relative '../core/conj2'
5
+ require_relative '../core/goal_template'
6
+ require_relative '../core/variable'
7
+ require_relative 'fresh_env'
8
+
9
+ module MiniKraken
10
+ module Glue
11
+ # A combination of an Environment (= a scope for one or more variables)
12
+ # and a goal. It quacks like a Goal template object: when receiving the
13
+ # instantiate message, it creates a FreshEnv.
14
+ class FreshEnvFactory
15
+ # @return [Array<String>] The names of variables to be.
16
+ attr_reader :names
17
+
18
+ # @return [GoalTemplate] The goal template
19
+ attr_reader :goal_template
20
+
21
+ # @param theNames [Array<String>] The names of variables to build.
22
+ # @param aGoal [GoalTemplate, Array<Goal>] The goal template(s)
23
+ def initialize(theNames, aGoalTemplate)
24
+ @goal_template = valid_goal_template(aGoalTemplate)
25
+ @names = valid_names(theNames)
26
+ end
27
+
28
+ # Factory method: Create a goal object.
29
+ # @param formals [Array<FormalArg>] Array of formal arguments
30
+ # @param actuals [Array<GoalArg>] Array of actual arguments
31
+ # @return [Goal] instantiate a goal object given the actuals and environment
32
+ def instantiate(formals, actuals)
33
+ # require 'debug'
34
+ goal = goal_template.instantiate(formals, actuals)
35
+ FreshEnv.new(names, goal, false)
36
+ end
37
+
38
+ protected
39
+
40
+ def introspect
41
+ +", @names=[#{names.join(', ')}]"
42
+ end
43
+
44
+ private
45
+
46
+ def valid_names(theNames)
47
+ theNames
48
+ end
49
+
50
+ def valid_goal_template(aGoalTemplate)
51
+ result = nil
52
+
53
+ case aGoalTemplate
54
+ when FreshEnvFactory
55
+ result = aGoalTemplate
56
+ when Core::GoalTemplate
57
+ result = aGoalTemplate
58
+ # when Array # an Array of Goal?..
59
+ # goal_array = aGoalTemplate
60
+ # loop do
61
+ # conjunctions = []
62
+ # goal_array.each_slice(2) do |uno_duo|
63
+ # if uno_duo.size == 2
64
+ # conjunctions << Core::GoalTemplate.new(Core::Conj2.instance, uno_duo)
65
+ # else
66
+ # conjunctions << uno_duo[0]
67
+ # end
68
+ # end
69
+ # if conjunctions.size == 1
70
+ # result = conjunctions[0]
71
+ # break
72
+ # end
73
+ # goal_array = conjunctions
74
+ # end
75
+ else
76
+ raise StandardError, "Cannot handle argumment type #{aGoalTemplate.class}"
77
+ end
78
+
79
+ result
80
+ end
81
+ end # class
82
+ end # module
83
+ end # module
@@ -27,7 +27,7 @@ module MiniKraken
27
27
  outcome = solver.resume
28
28
  break if outcome.nil?
29
29
 
30
- env.propagate(outcome) if result.empty? && outcome.successful?
30
+ env.propagate(outcome) if result.empty? && outcome.success?
31
31
  result << build_solution(outcome)
32
32
  end
33
33
 
@@ -38,11 +38,9 @@ module MiniKraken
38
38
 
39
39
  # @return [Array] A vector of assignment for each variable
40
40
  def build_solution(outcome)
41
- sol = env.vars.values.map do |var|
42
- outcome.successful? ? var.quote(outcome) : nil
41
+ env.vars.values.map do |var|
42
+ outcome.success? ? var.quote(outcome) : nil
43
43
  end
44
-
45
- sol
46
44
  end
47
45
 
48
46
  # Transform the solutions into sequence of conscells.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.1.12'
4
+ VERSION = '0.2.03'
5
5
  end
@@ -9,6 +9,7 @@ module PkgExtending
9
9
  def self.pkg_files(aPackage)
10
10
  file_list = Dir[
11
11
  '.rspec',
12
+ '.rubocop.yml',
12
13
  '.travis.yml',
13
14
  'Gemfile',
14
15
  'Rakefile',
@@ -19,7 +20,8 @@ module PkgExtending
19
20
  'bin/*.rb',
20
21
  'lib/*.*',
21
22
  'lib/**/*.rb',
22
- 'spec/**/*.rb'
23
+ 'spec/**/*.rb',
24
+ 'spec/.rubocop.yml'
23
25
  ]
24
26
  aPackage.files = file_list
25
27
  aPackage.test_files = Dir['spec/**/*_spec.rb']
@@ -38,8 +40,8 @@ Gem::Specification.new do |spec|
38
40
  spec.authors = ['Dimitri Geshef']
39
41
  spec.email = ['famished.tiger@yahoo.com']
40
42
 
41
- spec.summary = %q{Implementation of Minikanren language in Ruby. WIP}
42
- spec.description = %q{Implementation of Minikanren language in Ruby. WIP}
43
+ spec.summary = 'Implementation of Minikanren language in Ruby. WIP'
44
+ spec.description = 'Implementation of Minikanren language in Ruby. WIP'
43
45
  spec.homepage = 'https://github.com/famished-tiger/mini_kraken'
44
46
  spec.license = 'MIT'
45
47
 
@@ -47,6 +49,7 @@ Gem::Specification.new do |spec|
47
49
  spec.bindir = 'exe'
48
50
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
49
51
  spec.require_paths = ['lib']
52
+ spec.required_ruby_version = '~> 2.4'
50
53
 
51
54
  PkgExtending.pkg_files(spec)
52
55
  PkgExtending.pkg_documentation(spec)
@@ -0,0 +1,13 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ # RSpec expectation lines can be very lenghty
4
+ Layout/LineLength:
5
+ Max: 99
6
+
7
+ # RSpec contexts can be very lenghty
8
+ Metrics/BlockLength:
9
+ Max: 1000
10
+
11
+ # RSpec modules can be very lenghty
12
+ Metrics/ModuleLength:
13
+ Max: 1000
@@ -58,9 +58,9 @@ module MiniKraken
58
58
  expect { subject.solver_for([pea, succeeds], env) }.to raise_error(err)
59
59
  end
60
60
 
61
- it 'should fails if when all goals fail' do
61
+ it 'should fail when all goals fail' do
62
62
  solver = subject.solver_for([fails, fails, fails], env)
63
- expect(solver.resume).not_to be_successful
63
+ expect(solver.resume).not_to be_success
64
64
  expect(solver.resume).to be_nil
65
65
  end
66
66
 
@@ -68,7 +68,7 @@ module MiniKraken
68
68
  subgoal = Goal.new(Equals.instance, [olive, ref_q])
69
69
  solver = subject.solver_for([subgoal, fails, fails], env)
70
70
  outcome = solver.resume
71
- expect(outcome).to be_successful
71
+ expect(outcome).to be_success
72
72
  expect(outcome.associations['q'].first.value).to eq(olive)
73
73
  expect(solver.resume).to be_nil
74
74
  end
@@ -77,7 +77,7 @@ module MiniKraken
77
77
  subgoal = Goal.new(Equals.instance, [oil, ref_q])
78
78
  solver = subject.solver_for([fails, subgoal, fails], env)
79
79
  outcome = solver.resume
80
- expect(outcome).to be_successful
80
+ expect(outcome).to be_success
81
81
  expect(outcome.associations['q'].first.value).to eq(oil)
82
82
  expect(solver.resume).to be_nil
83
83
  end
@@ -86,7 +86,7 @@ module MiniKraken
86
86
  subgoal = Goal.new(Equals.instance, [oil, ref_q])
87
87
  solver = subject.solver_for([fails, fails, subgoal], env)
88
88
  outcome = solver.resume
89
- expect(outcome).to be_successful
89
+ expect(outcome).to be_success
90
90
  expect(outcome.associations['q'].first.value).to eq(oil)
91
91
  expect(solver.resume).to be_nil
92
92
  end
@@ -100,17 +100,17 @@ module MiniKraken
100
100
 
101
101
  # First solution
102
102
  outcome1 = solver.resume
103
- expect(outcome1).to be_successful
103
+ expect(outcome1).to be_success
104
104
  expect(outcome1.associations['q'].first.value).to eq(olive)
105
105
 
106
106
  # Second solution
107
107
  outcome2 = solver.resume
108
- expect(outcome2).to be_successful
108
+ expect(outcome2).to be_success
109
109
  expect(outcome2.associations['q'].first.value).to eq(oil)
110
110
 
111
111
  # Third solution
112
112
  outcome3 = solver.resume
113
- expect(outcome3).to be_successful
113
+ expect(outcome3).to be_success
114
114
  expect(outcome3.associations['q'].first.value).to eq(pea)
115
115
 
116
116
  expect(solver.resume).to be_nil
@@ -129,13 +129,13 @@ module MiniKraken
129
129
 
130
130
  # First solution
131
131
  outcome1 = solver.resume
132
- expect(outcome1).to be_successful
132
+ expect(outcome1).to be_success
133
133
  expect(outcome1.associations['x'].first.value).to eq(split)
134
134
  expect(outcome1.associations['y'].first.value).to eq(pea)
135
135
 
136
136
  # Second solution
137
137
  outcome2 = solver.resume
138
- expect(outcome2).to be_successful
138
+ expect(outcome2).to be_success
139
139
  expect(outcome2.associations['x'].first.value).to eq(red)
140
140
  expect(outcome2.associations['y'].first.value).to eq(bean)
141
141
 
@@ -47,12 +47,12 @@ module MiniKraken
47
47
  it 'should yield one failure if one of the goal is fail' do
48
48
  # Fail as first argument
49
49
  solver = subject.solver_for([fails, succeeds], env)
50
- expect(solver.resume).not_to be_successful
50
+ expect(solver.resume).not_to be_success
51
51
  expect(solver.resume).to be_nil
52
52
 
53
53
  # Fail as second argument
54
54
  solver = subject.solver_for([succeeds, fails], env)
55
- expect(solver.resume).not_to be_successful
55
+ expect(solver.resume).not_to be_success
56
56
  expect(solver.resume).to be_nil
57
57
  end
58
58
 
@@ -60,7 +60,7 @@ module MiniKraken
60
60
  # Covers frame 1-50
61
61
  solver = subject.solver_for([succeeds, succeeds], env)
62
62
  outcome = solver.resume
63
- expect(outcome).to be_successful
63
+ expect(outcome).to be_success
64
64
  expect(outcome.associations).to be_empty
65
65
  expect(solver.resume).to be_nil
66
66
  end
@@ -71,7 +71,7 @@ module MiniKraken
71
71
  sub_goal = Goal.new(Equals.instance, [corn, ref_q])
72
72
  solver = subject.solver_for([succeeds, sub_goal], env)
73
73
  outcome = solver.resume
74
- expect(outcome).to be_successful
74
+ expect(outcome).to be_success
75
75
  expect(outcome.associations).not_to be_empty
76
76
  expect(outcome.associations['q'].first.value).to eq(corn)
77
77
  end
@@ -82,7 +82,7 @@ module MiniKraken
82
82
  sub_goal = Goal.new(Equals.instance, [corn, ref_q])
83
83
  solver = subject.solver_for([fails, sub_goal], env)
84
84
  outcome = solver.resume
85
- expect(outcome).not_to be_successful
85
+ expect(outcome).not_to be_success
86
86
  expect(outcome.associations).to be_empty
87
87
  end
88
88
 
@@ -93,7 +93,7 @@ module MiniKraken
93
93
  sub_goal2 = Goal.new(Equals.instance, [meal, ref_q])
94
94
  solver = subject.solver_for([sub_goal1, sub_goal2], env)
95
95
  outcome = solver.resume
96
- expect(outcome).not_to be_successful
96
+ expect(outcome).not_to be_success
97
97
  expect(outcome.associations).to be_empty
98
98
  end
99
99
 
@@ -104,7 +104,7 @@ module MiniKraken
104
104
  sub_goal2 = Goal.new(Equals.instance, [corn, ref_q])
105
105
  solver = subject.solver_for([sub_goal1, sub_goal2], env)
106
106
  outcome = solver.resume
107
- expect(outcome).to be_successful
107
+ expect(outcome).to be_success
108
108
  expect(outcome.associations).not_to be_empty
109
109
  expect(outcome.associations['q'].first.value).to eq(corn)
110
110
  end
@@ -11,6 +11,7 @@ module MiniKraken
11
11
  describe ConsCell do
12
12
  let(:pea) { KSymbol.new(:pea) }
13
13
  let(:pod) { KSymbol.new(:pod) }
14
+ let(:corn) { KSymbol.new(:corn) }
14
15
  subject { ConsCell.new(pea, pod) }
15
16
 
16
17
  context 'Initialization:' do
@@ -39,6 +40,12 @@ module MiniKraken
39
40
  expect(ConsCell.new(nil, nil)).to be_null
40
41
  expect(NullList).to be_null
41
42
  end
43
+
44
+ it 'simplifies cdr if its referencing a null list' do
45
+ instance = ConsCell.new(pea, NullList)
46
+ expect(instance.car).to eq(pea)
47
+ expect(instance.cdr).to be_nil
48
+ end
42
49
  end # context
43
50
 
44
51
  context 'Provided services:' do
@@ -66,6 +73,34 @@ module MiniKraken
66
73
  expect(instance.car).to eq(pea)
67
74
  expect(instance.cdr).to eq(trail)
68
75
  end
76
+
77
+ it 'should provide a list representation of itself' do
78
+ # Case of null list
79
+ expect(NullList.to_s).to eq '()'
80
+
81
+ # Case of one element proper list
82
+ cell = ConsCell.new(pea)
83
+ expect(cell.to_s).to eq '(:pea)'
84
+
85
+ # Case of two elements proper list
86
+ cell = ConsCell.new(pea, ConsCell.new(pod))
87
+ expect(cell.to_s).to eq '(:pea :pod)'
88
+
89
+ # Case of two elements improper list
90
+ expect(subject.to_s).to eq '(:pea . :pod)'
91
+
92
+ # Case of three elements proper list
93
+ cell = ConsCell.new(pea, ConsCell.new(pod, ConsCell.new(corn)))
94
+ expect(cell.to_s).to eq '(:pea :pod :corn)'
95
+
96
+ # Case of three elements improper list
97
+ cell = ConsCell.new(pea, ConsCell.new(pod, corn))
98
+ expect(cell.to_s).to eq '(:pea :pod . :corn)'
99
+
100
+ # Case of a nested list
101
+ cell = ConsCell.new(ConsCell.new(pea), ConsCell.new(pod))
102
+ expect(cell.to_s).to eq '((:pea) :pod)'
103
+ end
69
104
  end # context
70
105
  end # describe
71
106
  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/variable_ref'
6
+
7
+ # Load the class under test
8
+ require_relative '../../lib/mini_kraken/core/cons_cell_visitor'
9
+
10
+
11
+ module MiniKraken
12
+ module Core
13
+ describe ConsCellVisitor do
14
+ let(:pea) { KSymbol.new(:pea) }
15
+ let(:pod) { KSymbol.new(:pod) }
16
+ let(:corn) { KSymbol.new(:corn) }
17
+ let(:ref_q) { VariableRef.new('q') }
18
+ let(:l_pea) { ConsCell.new(pea) }
19
+ let(:l_pea_pod) { ConsCell.new(pea, ConsCell.new(pod)) }
20
+ let(:l_pea_pod_corn) { ConsCell.new(pea, ConsCell.new(pod, ConsCell.new(corn))) }
21
+ subject { ConsCellVisitor }
22
+
23
+ context 'Provided services:' do
24
+ it 'acts as a factory of Enumerator' do
25
+ expect(subject.df_visitor(l_pea)).to be_kind_of(Fiber)
26
+ end
27
+ end # context
28
+
29
+ context 'proper list visiting:' do
30
+ it 'can visit a null list' do
31
+ null_list = ConsCell.new(nil)
32
+ visitor = subject.df_visitor(null_list)
33
+ expect(visitor.resume).to eq([:car, null_list])
34
+ expect(visitor.resume).to eq([:car, nil])
35
+ expect(visitor.resume).to eq([:cdr, nil])
36
+ expect(visitor.resume).to eq([:stop, nil])
37
+ end
38
+
39
+ it 'can visit a single element proper list' do
40
+ visitor = subject.df_visitor(l_pea)
41
+ expect(visitor.resume).to eq([:car, l_pea])
42
+ expect(visitor.resume).to eq([:car, pea])
43
+ expect(visitor.resume).to eq([:cdr, nil])
44
+ expect(visitor.resume).to eq([:stop, nil])
45
+ end
46
+
47
+ it 'can visit a two elements proper list' do
48
+ visitor = subject.df_visitor(l_pea_pod)
49
+ expect(visitor.resume).to eq([:car, l_pea_pod])
50
+ expect(visitor.resume).to eq([:car, pea])
51
+ expect(visitor.resume).to eq([:cdr, l_pea_pod.cdr])
52
+ expect(visitor.resume).to eq([:car, pod])
53
+ expect(visitor.resume).to eq([:cdr, nil])
54
+ expect(visitor.resume).to eq([:stop, nil])
55
+ end
56
+
57
+ it 'can visit a three elements proper list' do
58
+ visitor = subject.df_visitor(l_pea_pod_corn)
59
+ expect(visitor.resume).to eq([:car, l_pea_pod_corn])
60
+ expect(visitor.resume).to eq([:car, pea])
61
+ expect(visitor.resume).to eq([:cdr, l_pea_pod_corn.cdr])
62
+ expect(visitor.resume).to eq([:car, pod])
63
+ expect(visitor.resume).to eq([:cdr, l_pea_pod_corn.cdr.cdr])
64
+ expect(visitor.resume).to eq([:car, corn])
65
+ expect(visitor.resume).to eq([:cdr, nil])
66
+ expect(visitor.resume).to eq([:stop, nil])
67
+ end
68
+ end # context
69
+
70
+ context 'improper list visiting:' do
71
+ it 'can visit a single element improper list' do
72
+ l_improper = ConsCell.new(nil, pea)
73
+ visitor = subject.df_visitor(l_improper)
74
+ expect(visitor.resume).to eq([:car, l_improper])
75
+ expect(visitor.resume).to eq([:car, nil])
76
+ expect(visitor.resume).to eq([:cdr, pea])
77
+ expect(visitor.resume).to eq([:stop, nil])
78
+ end
79
+
80
+ it 'can visit a two elements improper list' do
81
+ l_improper = ConsCell.new(pea, pod)
82
+ visitor = subject.df_visitor(l_improper)
83
+ expect(visitor.resume).to eq([:car, l_improper])
84
+ expect(visitor.resume).to eq([:car, pea])
85
+ expect(visitor.resume).to eq([:cdr, pod])
86
+ expect(visitor.resume).to eq([:stop, nil])
87
+ end
88
+
89
+ it 'can visit a three elements improper list' do
90
+ l_improper = ConsCell.new(pea, ConsCell.new(pod, corn))
91
+ visitor = subject.df_visitor(l_improper)
92
+ expect(visitor.resume).to eq([:car, l_improper])
93
+ expect(visitor.resume).to eq([:car, pea])
94
+ expect(visitor.resume).to eq([:cdr, l_improper.cdr])
95
+ expect(visitor.resume).to eq([:car, pod])
96
+ expect(visitor.resume).to eq([:cdr, corn])
97
+ expect(visitor.resume).to eq([:stop, nil])
98
+ end
99
+ end # context
100
+
101
+ context 'Skip visit of children of a ConsCell:' do
102
+ it 'can skip the visit of null list children' do
103
+ null_list = ConsCell.new(nil)
104
+ visitor = subject.df_visitor(null_list)
105
+
106
+ # Tell to skip children by passing a true value to resume
107
+ expect(visitor.resume(true)).to eq([:car, null_list])
108
+ expect(visitor.resume).to eq([:stop, nil])
109
+ end
110
+
111
+ it 'can skip the visit of some children' do
112
+ tree = ConsCell.new(pea, ConsCell.new(l_pea_pod, ConsCell.new(corn)))
113
+ expect(tree.to_s).to eq('(:pea (:pea :pod) :corn)')
114
+ visitor = subject.df_visitor(tree)
115
+ expect(visitor.resume).to eq([:car, tree])
116
+ expect(visitor.resume).to eq([:car, pea])
117
+ expect(visitor.resume).to eq([:cdr, tree.cdr])
118
+ expect(visitor.resume).to eq([:car, l_pea_pod])
119
+
120
+ # Tell to skip children by passing a true value to resume
121
+ expect(visitor.resume(true)).to eq([:cdr, tree.cdr.cdr])
122
+ expect(visitor.resume).to eq([:car, corn])
123
+ expect(visitor.resume).to eq([:cdr, nil])
124
+ expect(visitor.resume).to eq([:stop, nil])
125
+ end
126
+ end # context
127
+
128
+ context 'Circular structures visiting:' do
129
+ it 'should cope with a circular graph' do
130
+ second_cell = ConsCell.new(pod)
131
+ first_cell = ConsCell.new(pea, second_cell)
132
+ second_cell.instance_variable_set(:@car, first_cell) # Ugly!
133
+
134
+ visitor = subject.df_visitor(first_cell)
135
+ expect(visitor.resume).to eq([:car, first_cell])
136
+ expect(visitor.resume).to eq([:car, pea])
137
+ expect(visitor.resume).to eq([:cdr, second_cell])
138
+ expect(visitor.resume).to eq([:cdr, nil]) # Skip car (was already visited)
139
+ expect(visitor.resume).to eq([:stop, nil])
140
+ end
141
+ end # context
142
+ end # describe
143
+ end # module
144
+ end # module