mini_kraken 0.2.02 → 0.3.02

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +52 -0
  4. data/README.md +19 -19
  5. data/lib/mini_kraken.rb +0 -1
  6. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  7. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  8. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  9. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  10. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  11. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  12. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  13. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  14. data/lib/mini_kraken/composite/cons_cell.rb +299 -0
  15. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  16. data/lib/mini_kraken/composite/list.rb +32 -0
  17. data/lib/mini_kraken/core/all_core.rb +8 -0
  18. data/lib/mini_kraken/core/any_value.rb +31 -7
  19. data/lib/mini_kraken/core/arity.rb +69 -0
  20. data/lib/mini_kraken/core/association.rb +29 -4
  21. data/lib/mini_kraken/core/association_copy.rb +50 -0
  22. data/lib/mini_kraken/core/base_term.rb +13 -0
  23. data/lib/mini_kraken/core/blackboard.rb +315 -0
  24. data/lib/mini_kraken/core/bookmark.rb +46 -0
  25. data/lib/mini_kraken/core/context.rb +492 -0
  26. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  27. data/lib/mini_kraken/core/entry.rb +40 -0
  28. data/lib/mini_kraken/core/fail.rb +20 -18
  29. data/lib/mini_kraken/core/fusion.rb +29 -0
  30. data/lib/mini_kraken/core/goal.rb +20 -29
  31. data/lib/mini_kraken/core/log_var.rb +22 -0
  32. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  33. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  34. data/lib/mini_kraken/core/parametrized_term.rb +61 -0
  35. data/lib/mini_kraken/core/relation.rb +14 -28
  36. data/lib/mini_kraken/core/scope.rb +67 -0
  37. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  38. data/lib/mini_kraken/core/specification.rb +48 -0
  39. data/lib/mini_kraken/core/succeed.rb +21 -17
  40. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  41. data/lib/mini_kraken/core/term.rb +15 -4
  42. data/lib/mini_kraken/glue/dsl.rb +45 -81
  43. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  44. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  45. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  46. data/lib/mini_kraken/rela/conde.rb +143 -0
  47. data/lib/mini_kraken/rela/conj2.rb +65 -0
  48. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  49. data/lib/mini_kraken/rela/disj2.rb +70 -0
  50. data/lib/mini_kraken/rela/fresh.rb +98 -0
  51. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  52. data/lib/mini_kraken/rela/unify.rb +258 -0
  53. data/lib/mini_kraken/version.rb +1 -1
  54. data/mini_kraken.gemspec +2 -2
  55. data/spec/.rubocop.yml +1 -1
  56. data/spec/atomic/atomic_term_spec.rb +98 -0
  57. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  58. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  59. data/spec/composite/cons_cell_spec.rb +225 -0
  60. data/spec/composite/cons_cell_visitor_spec.rb +158 -0
  61. data/spec/composite/list_spec.rb +50 -0
  62. data/spec/core/any_value_spec.rb +52 -0
  63. data/spec/core/arity_spec.rb +92 -0
  64. data/spec/core/association_copy_spec.rb +69 -0
  65. data/spec/core/association_spec.rb +31 -4
  66. data/spec/core/blackboard_spec.rb +287 -0
  67. data/spec/core/bookmark_spec.rb +40 -0
  68. data/spec/core/context_spec.rb +245 -0
  69. data/spec/core/core_spec.rb +40 -0
  70. data/spec/core/duck_fiber_spec.rb +16 -46
  71. data/spec/core/fail_spec.rb +5 -6
  72. data/spec/core/goal_spec.rb +24 -14
  73. data/spec/core/log_var_ref_spec.rb +105 -0
  74. data/spec/core/log_var_spec.rb +64 -0
  75. data/spec/core/nullary_relation_spec.rb +33 -0
  76. data/spec/core/parametrized_tem_spec.rb +39 -0
  77. data/spec/core/relation_spec.rb +33 -0
  78. data/spec/core/scope_spec.rb +73 -0
  79. data/spec/core/solver_adapter_spec.rb +70 -0
  80. data/spec/core/specification_spec.rb +43 -0
  81. data/spec/core/succeed_spec.rb +5 -5
  82. data/spec/core/symbol_table_spec.rb +142 -0
  83. data/spec/glue/dsl_chap1_spec.rb +96 -144
  84. data/spec/glue/dsl_chap2_spec.rb +350 -0
  85. data/spec/glue/run_star_expression_spec.rb +82 -906
  86. data/spec/rela/conde_spec.rb +153 -0
  87. data/spec/rela/conj2_spec.rb +123 -0
  88. data/spec/rela/def_relation_spec.rb +119 -0
  89. data/spec/rela/disj2_spec.rb +117 -0
  90. data/spec/rela/fresh_spec.rb +147 -0
  91. data/spec/rela/unify_spec.rb +369 -0
  92. data/spec/support/factory_atomic.rb +29 -0
  93. data/spec/support/factory_composite.rb +21 -0
  94. data/spec/support/factory_methods.rb +11 -26
  95. metadata +100 -64
  96. data/lib/mini_kraken/core/association_walker.rb +0 -183
  97. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  98. data/lib/mini_kraken/core/base_arg.rb +0 -10
  99. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  100. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  101. data/lib/mini_kraken/core/composite_term.rb +0 -41
  102. data/lib/mini_kraken/core/conde.rb +0 -143
  103. data/lib/mini_kraken/core/conj2.rb +0 -79
  104. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  105. data/lib/mini_kraken/core/def_relation.rb +0 -50
  106. data/lib/mini_kraken/core/designation.rb +0 -55
  107. data/lib/mini_kraken/core/disj2.rb +0 -72
  108. data/lib/mini_kraken/core/environment.rb +0 -73
  109. data/lib/mini_kraken/core/equals.rb +0 -156
  110. data/lib/mini_kraken/core/formal_arg.rb +0 -22
  111. data/lib/mini_kraken/core/formal_ref.rb +0 -25
  112. data/lib/mini_kraken/core/freshness.rb +0 -45
  113. data/lib/mini_kraken/core/goal_arg.rb +0 -12
  114. data/lib/mini_kraken/core/goal_template.rb +0 -62
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -53
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -78
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -442
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -75
  121. data/spec/core/association_walker_spec.rb +0 -192
  122. data/spec/core/conde_spec.rb +0 -147
  123. data/spec/core/conj2_spec.rb +0 -114
  124. data/spec/core/cons_cell_spec.rb +0 -107
  125. data/spec/core/def_relation_spec.rb +0 -96
  126. data/spec/core/disj2_spec.rb +0 -99
  127. data/spec/core/environment_spec.rb +0 -142
  128. data/spec/core/equals_spec.rb +0 -304
  129. data/spec/core/goal_template_spec.rb +0 -74
  130. data/spec/core/outcome_spec.rb +0 -48
  131. data/spec/core/variable_ref_spec.rb +0 -27
  132. data/spec/core/variable_spec.rb +0 -35
  133. data/spec/core/vocabulary_spec.rb +0 -219
  134. data/spec/glue/fresh_env_spec.rb +0 -62
@@ -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,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'relation'
3
+ require_relative '../core/goal'
4
+ require_relative '../core/relation'
4
5
 
5
6
  module MiniKraken
6
- module Core
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
- raise StandardError, prefix + "'#{arg.class}'"
18
+ raise StandardError, prefix + "'#{arg.class}': #{arg}"
21
19
  end
22
20
  end
23
21
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniKraken
4
- VERSION = '0.2.02'
4
+ VERSION = '0.3.02'
5
5
  end
@@ -40,8 +40,8 @@ Gem::Specification.new do |spec|
40
40
  spec.authors = ['Dimitri Geshef']
41
41
  spec.email = ['famished.tiger@yahoo.com']
42
42
 
43
- spec.summary = 'Implementation of Minikanren language in Ruby. WIP'
44
- spec.description = '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'
45
45
  spec.homepage = 'https://github.com/famished-tiger/mini_kraken'
46
46
  spec.license = 'MIT'
47
47
 
@@ -2,7 +2,7 @@ inherit_from: ../.rubocop.yml
2
2
 
3
3
  # RSpec expectation lines can be very lenghty
4
4
  Layout/LineLength:
5
- Max: 99
5
+ Max: 90
6
6
 
7
7
  # RSpec contexts can be very lenghty
8
8
  Metrics/BlockLength:
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require 'ostruct'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/mini_kraken/atomic/atomic_term'
8
+
9
+ module MiniKraken
10
+ module Atomic
11
+ describe AtomicTerm do
12
+ let(:a_value) { :serenity }
13
+ let(:other_value) { :fuzziness }
14
+ subject { AtomicTerm.new(a_value) }
15
+
16
+ context 'Initialization:' do
17
+ it 'should be created with a Ruby datatype instance' do
18
+ expect { AtomicTerm.new(a_value) }.not_to raise_error
19
+ end
20
+
21
+ it 'knows its value' do
22
+ expect(subject.value).to eq(a_value)
23
+ end
24
+
25
+ it 'freezes its value' do
26
+ expect(subject.value).to be_frozen
27
+ end
28
+ end # context
29
+
30
+ context 'Provided services:' do
31
+ it 'should know that it is a pinned term' do
32
+ ctx = double('mock-ctx')
33
+ expect(subject.pinned?(ctx)).to be_truthy
34
+ end
35
+
36
+ it 'should know that it is not a floating term' do
37
+ ctx = double('mock-ctx')
38
+ expect(subject.floating?(ctx)).to be_falsy
39
+ end
40
+
41
+ it 'should know that it is not an unbound term' do
42
+ ctx = double('mock-ctx')
43
+ expect(subject.unbound?(ctx)).to be_falsy
44
+ end
45
+
46
+ it 'performs data value comparison' do
47
+ itself = subject # Renaming as workaround to Rubocop complaint
48
+ expect(subject == itself).to be_truthy
49
+ expect(subject == subject.value).to be_truthy
50
+
51
+ expect(subject == other_value).to be_falsy
52
+ expect(subject == AtomicTerm.new(other_value)).to be_falsy
53
+
54
+ # Same duck type, same value
55
+ yet_another = OpenStruct.new(value: a_value)
56
+ expect(subject == yet_another).to be_truthy
57
+
58
+ # Same duck type, different value
59
+ still_another = OpenStruct.new(value: other_value)
60
+ expect(subject == still_another).to be_falsy
61
+ end
62
+
63
+ it 'performs type and data value comparison' do
64
+ expect(subject).to be_eql(subject)
65
+
66
+ # Same type, same value
67
+ other = AtomicTerm.new(a_value)
68
+ expect(subject).to be_eql(other)
69
+
70
+ # Same type, other value
71
+ another = AtomicTerm.new(other_value)
72
+ expect(subject).not_to be_eql(another)
73
+
74
+ # Different type, same value
75
+ yet_another = OpenStruct.new(value: other_value)
76
+ expect(subject).not_to be_eql(yet_another)
77
+ end
78
+
79
+ it 'returns itself when receiving quote message' do
80
+ ctx = double('mock-ctx')
81
+ expect(subject.quote(ctx)).to eq(subject)
82
+ end
83
+
84
+ it 'should know it has no dependency on variable(s)' do
85
+ ctx = double('mock-ctx')
86
+ expect(subject.dependencies(ctx)).to be_empty
87
+ end
88
+
89
+ it 'should dup itself' do
90
+ substitutions = double('fake-substitutions')
91
+ duplicate = subject.dup_cond(substitutions)
92
+ expect(duplicate).to eq(subject) # same value
93
+ expect(duplicate).not_to be_equal(subject) # different object ids
94
+ end
95
+ end # context
96
+ end # describe
97
+ end # module
98
+ end # module