mini_kraken 0.2.03 → 0.3.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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +378 -333
  3. data/CHANGELOG.md +48 -0
  4. data/README.md +29 -21
  5. data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
  6. data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
  7. data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
  8. data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
  9. data/lib/mini_kraken/atomic/k_string.rb +17 -0
  10. data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
  11. data/lib/mini_kraken/composite/all_composite.rb +4 -0
  12. data/lib/mini_kraken/composite/composite_term.rb +27 -0
  13. data/lib/mini_kraken/composite/cons_cell.rb +301 -0
  14. data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
  15. data/lib/mini_kraken/composite/list.rb +32 -0
  16. data/lib/mini_kraken/core/all_core.rb +8 -0
  17. data/lib/mini_kraken/core/any_value.rb +31 -7
  18. data/lib/mini_kraken/core/arity.rb +69 -0
  19. data/lib/mini_kraken/core/association.rb +29 -4
  20. data/lib/mini_kraken/core/association_copy.rb +50 -0
  21. data/lib/mini_kraken/core/base_term.rb +13 -0
  22. data/lib/mini_kraken/core/blackboard.rb +315 -0
  23. data/lib/mini_kraken/core/bookmark.rb +46 -0
  24. data/lib/mini_kraken/core/context.rb +492 -0
  25. data/lib/mini_kraken/core/duck_fiber.rb +21 -19
  26. data/lib/mini_kraken/core/entry.rb +40 -0
  27. data/lib/mini_kraken/core/fail.rb +20 -18
  28. data/lib/mini_kraken/core/fusion.rb +29 -0
  29. data/lib/mini_kraken/core/goal.rb +20 -29
  30. data/lib/mini_kraken/core/log_var.rb +22 -0
  31. data/lib/mini_kraken/core/log_var_ref.rb +108 -0
  32. data/lib/mini_kraken/core/nullary_relation.rb +2 -9
  33. data/lib/mini_kraken/core/parametrized_term.rb +68 -0
  34. data/lib/mini_kraken/core/relation.rb +14 -28
  35. data/lib/mini_kraken/core/scope.rb +67 -0
  36. data/lib/mini_kraken/core/solver_adapter.rb +58 -0
  37. data/lib/mini_kraken/core/specification.rb +48 -0
  38. data/lib/mini_kraken/core/succeed.rb +21 -17
  39. data/lib/mini_kraken/core/symbol_table.rb +137 -0
  40. data/lib/mini_kraken/core/term.rb +15 -4
  41. data/lib/mini_kraken/glue/dsl.rb +44 -88
  42. data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
  43. data/lib/mini_kraken/rela/all_rela.rb +8 -0
  44. data/lib/mini_kraken/rela/binary_relation.rb +30 -0
  45. data/lib/mini_kraken/rela/conde.rb +143 -0
  46. data/lib/mini_kraken/rela/conj2.rb +65 -0
  47. data/lib/mini_kraken/rela/def_relation.rb +93 -0
  48. data/lib/mini_kraken/rela/disj2.rb +70 -0
  49. data/lib/mini_kraken/rela/fresh.rb +98 -0
  50. data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
  51. data/lib/mini_kraken/rela/unify.rb +265 -0
  52. data/lib/mini_kraken/version.rb +1 -1
  53. data/mini_kraken.gemspec +2 -2
  54. data/spec/.rubocop.yml +1 -1
  55. data/spec/atomic/atomic_term_spec.rb +98 -0
  56. data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
  57. data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
  58. data/spec/composite/cons_cell_spec.rb +225 -0
  59. data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
  60. data/spec/composite/list_spec.rb +50 -0
  61. data/spec/core/any_value_spec.rb +52 -0
  62. data/spec/core/arity_spec.rb +92 -0
  63. data/spec/core/association_copy_spec.rb +69 -0
  64. data/spec/core/association_spec.rb +31 -4
  65. data/spec/core/blackboard_spec.rb +287 -0
  66. data/spec/core/bookmark_spec.rb +40 -0
  67. data/spec/core/context_spec.rb +245 -0
  68. data/spec/core/core_spec.rb +40 -0
  69. data/spec/core/duck_fiber_spec.rb +16 -46
  70. data/spec/core/fail_spec.rb +5 -6
  71. data/spec/core/goal_spec.rb +22 -12
  72. data/spec/core/log_var_ref_spec.rb +105 -0
  73. data/spec/core/log_var_spec.rb +64 -0
  74. data/spec/core/nullary_relation_spec.rb +33 -0
  75. data/spec/core/parametrized_tem_spec.rb +39 -0
  76. data/spec/core/relation_spec.rb +33 -0
  77. data/spec/core/scope_spec.rb +73 -0
  78. data/spec/core/solver_adapter_spec.rb +70 -0
  79. data/spec/core/specification_spec.rb +43 -0
  80. data/spec/core/succeed_spec.rb +5 -5
  81. data/spec/core/symbol_table_spec.rb +142 -0
  82. data/spec/glue/dsl_chap1_spec.rb +88 -144
  83. data/spec/glue/dsl_chap2_spec.rb +454 -19
  84. data/spec/glue/run_star_expression_spec.rb +81 -906
  85. data/spec/rela/conde_spec.rb +153 -0
  86. data/spec/rela/conj2_spec.rb +123 -0
  87. data/spec/rela/def_relation_spec.rb +119 -0
  88. data/spec/rela/disj2_spec.rb +117 -0
  89. data/spec/rela/fresh_spec.rb +147 -0
  90. data/spec/rela/unify_spec.rb +369 -0
  91. data/spec/support/factory_atomic.rb +29 -0
  92. data/spec/support/factory_composite.rb +21 -0
  93. data/spec/support/factory_methods.rb +11 -26
  94. metadata +98 -70
  95. data/lib/mini_kraken/core/association_walker.rb +0 -183
  96. data/lib/mini_kraken/core/atomic_term.rb +0 -67
  97. data/lib/mini_kraken/core/base_arg.rb +0 -10
  98. data/lib/mini_kraken/core/binary_relation.rb +0 -63
  99. data/lib/mini_kraken/core/composite_goal.rb +0 -46
  100. data/lib/mini_kraken/core/composite_term.rb +0 -41
  101. data/lib/mini_kraken/core/conde.rb +0 -143
  102. data/lib/mini_kraken/core/conj2.rb +0 -79
  103. data/lib/mini_kraken/core/cons_cell.rb +0 -82
  104. data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
  105. data/lib/mini_kraken/core/def_relation.rb +0 -53
  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 -193
  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 -102
  115. data/lib/mini_kraken/core/k_boolean.rb +0 -35
  116. data/lib/mini_kraken/core/outcome.rb +0 -63
  117. data/lib/mini_kraken/core/variable.rb +0 -41
  118. data/lib/mini_kraken/core/variable_ref.rb +0 -84
  119. data/lib/mini_kraken/core/vocabulary.rb +0 -446
  120. data/lib/mini_kraken/glue/fresh_env.rb +0 -103
  121. data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
  122. data/spec/core/association_walker_spec.rb +0 -192
  123. data/spec/core/conde_spec.rb +0 -147
  124. data/spec/core/conj2_spec.rb +0 -114
  125. data/spec/core/cons_cell_spec.rb +0 -107
  126. data/spec/core/def_relation_spec.rb +0 -97
  127. data/spec/core/disj2_spec.rb +0 -99
  128. data/spec/core/environment_spec.rb +0 -142
  129. data/spec/core/equals_spec.rb +0 -317
  130. data/spec/core/goal_template_spec.rb +0 -74
  131. data/spec/core/outcome_spec.rb +0 -56
  132. data/spec/core/variable_ref_spec.rb +0 -30
  133. data/spec/core/variable_spec.rb +0 -35
  134. data/spec/core/vocabulary_spec.rb +0 -219
  135. data/spec/glue/fresh_env_factory_spec.rb +0 -97
  136. data/spec/glue/fresh_env_spec.rb +0 -62
@@ -1,3 +1,51 @@
1
+ ## [0.3.03] - 2020-12-20
2
+ - `mini_kraken` now implements the `nullo`, `pairo` relations.
3
+ - Passes all examples from 2:1 up to 2:51 but 2:37
4
+
5
+ ### CHANGED
6
+ - Class `ConsCell`: default value for cdr is NullList instead of nil.
7
+ - Method `ConsCellVisitor#df_visitor`: no visit of car and cdr fiels of a null ConsCell
8
+ - Method `Unify#unify_references` now supports the unification of of floating references
9
+
10
+ ### FIXED
11
+ - Method `ParametrizedTerm#dup_cond` now performs replacement of Array elements.
12
+ - Method `Unify#unify_composite_terms` fixed typo in a method call
13
+ - Method `Unify#unify_references` fixed typo of exception class
14
+
15
+ ## [0.3.02] - 2020-12-19
16
+ - `mini_kraken` now implements the `conso` relation.
17
+
18
+ ### CHANGED
19
+ - Method `Context#build_solution` old source code (redundant) removed.
20
+ - File `README.md` minor changes.
21
+
22
+ ### FIXED
23
+ - Method `ConsCell#expand` pushed a node in the stack even if it was fully visited.
24
+
25
+ ## [0.3.01] - 2020-12-17
26
+ - Minor: updated Rubocop config file `.rubocop.yml`
27
+
28
+ ### CHANGED
29
+ - Config file `.rubocop.yml` updated to incorporate the cops from Rubocop 1.6.1
30
+ - Several files re-styled to please Rubocop
31
+ - `README.md` Checkbox for `cdro` is now ticked
32
+
33
+ ## [0.3.00] - 2020-12-16
34
+ - Version number bump because this is a vast code rewrite
35
+
36
+ ### CHANGED
37
+ - Most classes have been reworked
38
+ - Relation `Equals` has been renamed to `Unify`
39
+
40
+ ## [0.2.04] - 2020-09-02
41
+ - Intermediate version before vast code rework.
42
+
43
+ ### CHANGED
44
+ - Classes `KBoolean`, `KSymbol`, `KInteger` moved to namespace `Atomic`
45
+ - Classes `ConsCell`, `ConsCellVisitor` moved to namespace `Composite`
46
+ - Class `Variable` renamed to `LogVar`
47
+ - Class `VariableRef` renamed to `LogVarRef`
48
+
1
49
  ## [0.2.03] - 2020-09-02
2
50
  - The DSL (Domain Specific Language) supports the `caro` relation & passes frames up to 2-8 from Chapter 2.
3
51
 
data/README.md CHANGED
@@ -4,38 +4,46 @@
4
4
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/mini_kraken/blob/master/LICENSE.txt)
5
5
 
6
6
  ### What is __mini_kraken__ ?
7
- A library containing an implementation of the [miniKanren](http://minikanren.org/)
8
- relational programming language in Ruby.
9
- *miniKanren* is a small language for relational (logic) programming.
10
- Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
7
+ A library containing an implementation in Ruby of the [miniKanren](http://minikanren.org/)
8
+ language.
9
+ *miniKanren* is a small language for relational (logic) programming as defined in the "The Reasoned Schemer" book.
11
10
  Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
12
11
  ISBN: 9780262535519, (2018), MIT Press.
13
12
 
14
13
  ### Features
14
+ - Test suite patterned on examples from the reference book
15
15
  - Pure Ruby implementation, not a port from another language
16
16
  - Object-Oriented design
17
17
  - No runtime dependencies
18
18
 
19
19
  ### miniKanren Features
20
- - [X] ==
20
+ - [X] ( == ) unify
21
21
  - [X] run\*
22
22
  - [X] fresh
23
23
  - [X] conde
24
24
  - [X] conj2
25
25
  - [X] disj2
26
26
  - [X] defrel
27
- - [X] caro
27
+ - [X] caro
28
+ - [X] cdro
29
+ - [X] conso
30
+ - [X] nullo
31
+ - [X] pairo
28
32
 
29
33
  ### TODO
30
34
 
31
35
  - [ ] Occurs check
32
36
 
33
- List-centric relations from Chapter 2
34
- - [ ] cdro
35
- - [ ] conso
36
- - [ ] nullo
37
- - [ ] pairo
38
- - [ ] singletono
37
+ Pair-centric relations from Chapter 2
38
+ - [ ] singletono
39
+
40
+ List-centric relations from Chapter 3
41
+ - [ ] listo
42
+ - [ ] lolo
43
+ - [ ] loso
44
+ - [ ] membero
45
+ - [ ] proper_membero
46
+
39
47
 
40
48
  ## Installation
41
49
 
@@ -65,7 +73,7 @@ require 'mini_kraken' # Load MiniKraken library
65
73
 
66
74
  extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
67
75
 
68
- result = run_star('q', equals(q, :pea))
76
+ result = run_star('q', unify(q, :pea))
69
77
  puts result # => (:pea)
70
78
  ```
71
79
 
@@ -79,11 +87,11 @@ and satisfying one or more goals to the `run_star method.
79
87
  In our example, the `run_star` method instructs `MiniKraken` to find all solutions,
80
88
  knowing that each successful solution:
81
89
  - binds a value to the provided variable `q` and
82
- - meets the goal `equals(q, :pea)`.
90
+ - meets the goal `unify(q, :pea)`.
83
91
 
84
- The goal `equals(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
92
+ The goal `unify(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
85
93
  not yet bound to a value) and will be bound to the symbol `:pea` as a side effect
86
- of the goal `equals`.
94
+ of the goal `unify`.
87
95
 
88
96
  So the above program succeeds and the only found solution is obtained by binding
89
97
  the variable `q` to the value :pea. Hence the result of the `puts` method.
@@ -97,11 +105,11 @@ So the above program succeeds and the only found solution is obtained by binding
97
105
  extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
98
106
 
99
107
  # Following miniKanren program fails
100
- result = run_star('q', [equals(q, :pea), equals(q, :pod)])
108
+ result = run_star('q', [unify(q, :pea), unify(q, :pod)])
101
109
  puts result # => ()
102
110
  ```
103
111
  In this example, we learn that `run_star` can take multiple goals placed in an array.
104
- The program fails to find a solution since it is not possible to satisfy the two `equals` goals simultaneously.
112
+ The program fails to find a solution since it is not possible to satisfy the two `unify` goals simultaneously.
105
113
  In case of failure, the `run_star` returns an empty list represented as `()` in the output.
106
114
 
107
115
 
@@ -111,7 +119,7 @@ In case of failure, the `run_star` returns an empty list represented as `()` in
111
119
  ```ruby
112
120
  # In this example and following, one assumes that DSL is loaded as shown in Example 1
113
121
 
114
- result = run_star(['x', 'y'], [equals(:hello, x), equals(y, :world)])
122
+ result = run_star(['x', 'y'], [unify(:hello, x), unify(y, :world)])
115
123
  puts result # => ((:hello :world))
116
124
  ```
117
125
 
@@ -122,8 +130,8 @@ This time, `run_star` takes two logical variables -`x` and `y`- and successfully
122
130
  ```ruby
123
131
  result = run_star(['x', 'y'],
124
132
  [
125
- disj2(equals(x, :blue), equals(x, :red)),
126
- disj2(equals(y, :sea), equals(:mountain, y))
133
+ disj2(unify(x, :blue), unify(x, :red)),
134
+ disj2(unify(y, :sea), unify(:mountain, y))
127
135
  ])
128
136
  puts result # => ((:blue :sea) (:blue :mountain) (:red :sea) (:red :mountain))
129
137
  ```
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'k_boolean'
4
+ require_relative 'k_string'
5
+ require_relative 'k_symbol'
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/term'
4
+ # require_relative '../core/freshness'
5
+
6
+ module MiniKraken
7
+ # This module packages the atomic term classes, that is,
8
+ # the basic datatypes that cannot be decomposed
9
+ # in MiniKraken into simpler, smaller data values.
10
+ module Atomic
11
+ # An atomic term is an elementary MiniKraken term, a data value
12
+ # that cannot be decomposed into simpler MiniKraken term(s).
13
+ # Typically, an atomic term encapsulates a Ruby primitive data object.
14
+ # MiniKraken treats atomic terms as immutable objects.
15
+ class AtomicTerm < Core::Term
16
+ # @return [Object] Internal representation of a MiniKraken data value.
17
+ attr_reader :value
18
+
19
+ # Initialize an atomic term with the given data object.
20
+ # @param aValue [Object] Ruby representation of MiniKraken data value
21
+ def initialize(aValue)
22
+ super()
23
+ @value = aValue
24
+ @value.freeze
25
+ end
26
+
27
+ # An atomic term, by definition is bound to a definite value.
28
+ # @param _ctx [Context]
29
+ # @return [FalseClass]
30
+ def unbound?(_ctx)
31
+ false
32
+ end
33
+
34
+ # An atomic term has a definite value, therefore it is not floating.
35
+ # @param _ctx [Context]
36
+ # @return [FalseClass]
37
+ def floating?(_ctx)
38
+ false
39
+ end
40
+
41
+ # An atomic term is a pinned term: by definition, it has a definite
42
+ # value.
43
+ # @param _ctx [Context]
44
+ # @return [TrueClass]
45
+ def pinned?(_ctx)
46
+ true
47
+ end
48
+
49
+ # Return a String representation of the atomic term
50
+ # @return [String]
51
+ def to_s
52
+ value.to_s
53
+ end
54
+
55
+ # Treat this object as a data value.
56
+ # @return [AtomicTerm]
57
+ def quote(_ctx)
58
+ self
59
+ end
60
+
61
+ # Data equality testing
62
+ # @param other [AtomicTerm, #value]
63
+ # @return [Boolean]
64
+ def ==(other)
65
+ if other.respond_to?(:value)
66
+ value == other.value
67
+ else
68
+ value == other
69
+ end
70
+ end
71
+
72
+ # Type and data equality testing
73
+ # @param other [AtomicTerm]
74
+ # @return [Boolean]
75
+ def eql?(other)
76
+ (self.class == other.class) && value.eql?(other.value)
77
+ end
78
+
79
+ # Return the list of variable (i_names) that this term depends on.
80
+ # For atomic terms, there is no such dependencies.
81
+ # @param _ctx [Core::Context]
82
+ # @return [Set] an empty set
83
+ def dependencies(_ctx)
84
+ Set.new
85
+ end
86
+
87
+ # Make a copy of self with all the variable reference being
88
+ # replaced by the corresponding value in the Hash.
89
+ # @param _substitutions [Hash {String => Term}]
90
+ # @return [Term]
91
+ def dup_cond(_substitutions)
92
+ dup
93
+ end
94
+ end # class
95
+ end # module
96
+ end # module
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'atomic_term'
4
+
5
+ module MiniKraken
6
+ module Atomic
7
+ # A specialized atomic term that implements a boolean (true/false) value
8
+ # in MiniKraken.
9
+ class KBoolean < AtomicTerm
10
+ # Initialize a MiniKraken boolean with a given data value.
11
+ # @example
12
+ # # Initialize with a Ruby boolean
13
+ # truthy = KBoolean.new(true)
14
+ # falsey = KBoolean.new(false)
15
+ # # Initialize with a String inspired from canonical miniKanren
16
+ # truthy = KBoolean.new('#t') # In Scheme #t means true
17
+ # falsey = KBoolean.new('#f') # In Scheme #f means false
18
+ # # Initialize with a Symbol inspired from canonical miniKanren
19
+ # truthy = KBoolean.new(:"#t") # In Scheme #t means true
20
+ # falsey = KBoolean.new(:"#f") # In Scheme #f means false
21
+ # @param aValue [Boolean, String, Symbol] Ruby representation of boolean value.
22
+ def initialize(aValue)
23
+ super(validated_value(aValue))
24
+ end
25
+
26
+ private
27
+
28
+ def validated_value(aValue)
29
+ case aValue
30
+ when true, false
31
+ aValue
32
+ when :"#t", '#t'
33
+ true
34
+ when :"#f", '#f'
35
+ false
36
+ else
37
+ raise StandardError, "Invalid boolean literal '#{aValue}'"
38
+ end
39
+ end
40
+ end # class
41
+ end # module
42
+ end # module
@@ -3,14 +3,11 @@
3
3
  require_relative 'atomic_term'
4
4
 
5
5
  module MiniKraken
6
- module Core
6
+ module Atomic
7
7
  # A specialized atomic term that represents an integer value.
8
8
  # in MiniKraken
9
+ # @note As MiniKraken doesn't support integer values yet, this class is WIP.
9
10
  class KInteger < AtomicTerm
10
- # @param aValue [Integer] Ruby representation of integer value
11
- def initialize(aValue)
12
- super(aValue)
13
- end
14
11
  end # class
15
12
  end # module
16
13
  end # module
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'atomic_term'
4
+
5
+ module MiniKraken
6
+ module Atomic
7
+ # A specialized atomic term that represents a string value
8
+ # in MiniKraken.
9
+ class KString < AtomicTerm
10
+ # Returns a string representing the MiniKraken symbol.
11
+ # @return [String]
12
+ def to_s
13
+ value
14
+ end
15
+ end # class
16
+ end # module
17
+ end # module
@@ -3,15 +3,10 @@
3
3
  require_relative 'atomic_term'
4
4
 
5
5
  module MiniKraken
6
- module Core
7
- # A specialized atomic term that represents a symbolic value.
8
- # in MiniKraken
6
+ module Atomic
7
+ # A specialized atomic term that represents a symbolic value
8
+ # in MiniKraken.
9
9
  class KSymbol < AtomicTerm
10
- # @param aValue [Symbol] Ruby representation of symbol value
11
- def initialize(aValue)
12
- super(aValue)
13
- end
14
-
15
10
  # Returns the name or string corresponding to value.
16
11
  # @return [String]
17
12
  def id2name
@@ -19,6 +14,7 @@ module MiniKraken
19
14
  end
20
15
 
21
16
  # Returns a string representing the MiniKraken symbol.
17
+ # @return [String]
22
18
  def to_s
23
19
  ":#{id2name}"
24
20
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'list'
4
+ require_relative 'cons_cell_visitor'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/term'
4
+
5
+ module MiniKraken
6
+ # This module packages the composite term classes.
7
+ # These hold one or more MiniKanren objects.
8
+ module Composite
9
+ # An composite term is an Minikraken term that can be
10
+ # decomposed into simpler MiniKraken data value(s).
11
+ class CompositeTerm < Core::Term
12
+ # Abstract method (to override). Return the child terms.
13
+ # @return [Array<Term>]
14
+ def children
15
+ raise NotImplementedError, 'This method must re-defined in subclass(es).'
16
+ end
17
+
18
+ =begin
19
+ # @param env [Environment]
20
+ # @return [Boolean]
21
+ def fresh?(env)
22
+ env.fresh_value?(self)
23
+ end
24
+ =end
25
+ end # class
26
+ end # module
27
+ end # module
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require_relative 'composite_term'
6
+ require_relative 'cons_cell_visitor'
7
+
8
+ module MiniKraken
9
+ module Composite
10
+ # In Lisp dialects, a cons cell (or a pair) is a data structure with two
11
+ # fields named car and cdr (for historical reasons).
12
+ # Cons cells are the key ingredient for building lists in Lisp.
13
+ # A cons cell can be depicted as a box with two parts, car and cdr each
14
+ # containing a reference to another object.
15
+ # +-----------+
16
+ # | car | cdr |
17
+ # +--|-----|--+
18
+ # | |
19
+ # V V
20
+ # obj1 obj2
21
+ #
22
+ # The list (1 2 3) can be constructed as follows:
23
+ # +-----------+
24
+ # | car | cdr |
25
+ # +--|-----|--+
26
+ # | |
27
+ # V V
28
+ # 1 +-----------+
29
+ # | car | cdr |
30
+ # +--|-----|--+
31
+ # | |
32
+ # V V
33
+ # 2 +-----------+
34
+ # | car | cdr |
35
+ # +--|-----|--+
36
+ # | |
37
+ # V V
38
+ # 3 nil
39
+ class ConsCell < CompositeTerm
40
+ # The first slot in a ConsCell
41
+ # @return [Term]
42
+ attr_reader :car
43
+
44
+ # The second slot in a ConsCell
45
+ # @return [Term]
46
+ attr_reader :cdr
47
+
48
+ # Construct a new conscell whose car and cdr are obj1 and obj2.
49
+ # In Scheme, a list is terminated by a null list.
50
+ # In MiniKraken, a list is terminated by a Ruby nil.
51
+ # Therefore, when setting the cdr to the null list, the implementation
52
+ # will silently replace the null list by a nil.
53
+ # @param obj1 [Term, NilClass]
54
+ # @param obj2 [Term, NilClass]
55
+ def initialize(obj1, obj2 = NullList)
56
+ super()
57
+ @car = obj1
58
+ if obj2.kind_of?(ConsCell) && obj2.null?
59
+ @cdr = NullList
60
+ else
61
+ @cdr = obj2
62
+ end
63
+ end
64
+
65
+ # Specialized constructor for null list.
66
+ # @return [ConsCell] Null list
67
+ def self.null
68
+ new(nil, nil)
69
+ end
70
+
71
+ # Set one element of the pair
72
+ # @param member [Symbol]
73
+ # @param element [Term]
74
+ def set!(member, element)
75
+ case member
76
+ when :car
77
+ set_car!(element)
78
+ when :cdr
79
+ set_cdr!(element)
80
+ else
81
+ raise StandardError, "Undefined cons cell member #{member}"
82
+ end
83
+ end
84
+
85
+ # Change the car of ConsCell to 'element'.
86
+ # Analogue of set-car! procedure in Scheme.
87
+ # @param element [Term]
88
+ def set_car!(element)
89
+ @cdr = NullList if null?
90
+ @car = element
91
+ end
92
+
93
+ # Change the cdr of ConsCell to 'element'.
94
+ # Analogue of set-cdr! procedure in Scheme.
95
+ # @param element [Term]
96
+ def set_cdr!(element)
97
+ @cdr = element
98
+ end
99
+
100
+ def children
101
+ [car, cdr]
102
+ end
103
+
104
+ # Return true if it is an empty list, otherwise false.
105
+ # A list is empty, when both car and cdr fields are nil.
106
+ # @return [Boolean]
107
+ def null?
108
+ car.nil? && cdr.nil?
109
+ end
110
+
111
+ # Is the receiver an unbound variable?
112
+ # By definition, a composite isn't a variable.
113
+ # @param _ctx [Core::Context]
114
+ # @return [FalseClass]
115
+ def unbound?(_ctx)
116
+ false
117
+ end
118
+
119
+ # Does the composite have a variable that is itself floating?
120
+ # @return [Boolean]
121
+ def floating?(ctx)
122
+ !pinned?(ctx)
123
+ end
124
+
125
+ # Does the cons cell have a definite value?
126
+ # @return [Boolean]
127
+ def pinned?(ctx)
128
+ pinned_car = car.nil? || car.pinned?(ctx)
129
+ pinned_cdr = cdr.nil? || cdr.pinned?(ctx)
130
+
131
+ pinned_car && pinned_cdr
132
+ end
133
+
134
+ # Return true if car and cdr fields have the same values as the other
135
+ # ConsCell.
136
+ # @param other [ConsCell]
137
+ # @return [Boolean]
138
+ def ==(other)
139
+ return false unless other.respond_to?(:car)
140
+
141
+ (car == other.car) && (cdr == other.cdr)
142
+ end
143
+
144
+ # Test for type and data value equality.
145
+ # @param other [ConsCell]
146
+ # @return [Boolean]
147
+ def eql?(other)
148
+ (self.class == other.class) && car.eql?(other.car) && cdr.eql?(other.cdr)
149
+ end
150
+
151
+ # Return a data object that is a copy of the ConsCell
152
+ # @param anEnv [Core::Environment]
153
+ # @return [ConsCell]
154
+ def quote(anEnv)
155
+ return self if null?
156
+
157
+ new_car = car.nil? ? nil : car.quote(anEnv)
158
+ new_cdr = cdr.nil? ? nil : cdr.quote(anEnv)
159
+ ConsCell.new(new_car, new_cdr)
160
+ end
161
+
162
+ # Use the list notation from Lisp as a text representation.
163
+ # @return [String]
164
+ def to_s
165
+ return '()' if null?
166
+
167
+ "(#{pair_to_s})"
168
+ end
169
+
170
+ # Return the list of variable (i_names) that this term depends on.
171
+ # For a variable reference, it will return the i_names of its variable
172
+ # @param ctx [Core::Context]
173
+ # @return [Set<String>] A set of i_names
174
+ def dependencies(ctx)
175
+ deps = []
176
+ visitor = ConsCellVisitor.df_visitor(self)
177
+ skip_children = false
178
+ loop do
179
+ side, cell = visitor.resume(skip_children)
180
+ if cell.kind_of?(Core::LogVarRef)
181
+ deps << ctx.lookup(cell.name).i_name
182
+ skip_children = true
183
+ else
184
+ skip_children = false
185
+ end
186
+ break if side == :stop
187
+ end
188
+
189
+ Set.new(deps)
190
+ end
191
+
192
+ # @param ctx [Core::Context]
193
+ # @param theSubstitutions [Hash{String => Association}]
194
+ def expand(ctx, theSubstitutions)
195
+ head = curr_cell = nil
196
+ path = []
197
+
198
+ visitor = ConsCellVisitor.df_visitor(self)
199
+ skip_children = false
200
+
201
+ loop do
202
+ side, cell = visitor.resume(skip_children)
203
+ # next if cell == self
204
+ break if side == :stop
205
+
206
+ case cell
207
+ when ConsCell
208
+ new_cell = ConsCell.null
209
+ if curr_cell
210
+ curr_cell.set!(side, new_cell)
211
+ path.push(curr_cell) unless side == :cdr
212
+ else
213
+ head = new_cell
214
+ path.push(new_cell)
215
+ end
216
+ if side == :cdr && cell.null?
217
+ curr_cell = path.pop
218
+ else
219
+ curr_cell = new_cell
220
+ end
221
+
222
+ when Core::LogVarRef
223
+ # Is this robust?
224
+ if cell.i_name
225
+ i_name = cell.i_name
226
+ else
227
+ i_name = ctx.symbol_table.lookup(cell.name).i_name
228
+ end
229
+ expanded = ctx.expand_value_of(i_name, theSubstitutions)
230
+ curr_cell.set!(side, expanded)
231
+ curr_cell = path.pop if side == :cdr
232
+ else
233
+ curr_cell.set!(side, cell)
234
+ curr_cell = path.pop if side == :cdr
235
+ end
236
+ end
237
+
238
+ head
239
+ end
240
+
241
+
242
+ # @param ctx [Core::Context]
243
+ # @param theSubstitutions [Hash{String => Association}]
244
+
245
+
246
+ # File 'lib/mini_kraken/atomic/atomic_term.rb', line 91
247
+ # Make a copy of self with all the variable reference being replaced
248
+ # by the corresponding value in the Hash.
249
+ # @param substitutions [Hash {String => Term}]
250
+ # @return [ConsCell]
251
+ def dup_cond(substitutions)
252
+ head = curr_cell = nil
253
+ path = []
254
+
255
+ visitor = ConsCellVisitor.df_visitor(self) # Breadth-first!
256
+ skip_children = false
257
+
258
+ loop do
259
+ side, cell = visitor.resume(skip_children)
260
+ # next if cell == self
261
+ break if side == :stop
262
+
263
+ if cell.kind_of?(ConsCell)
264
+ new_cell = ConsCell.null
265
+ if curr_cell
266
+ curr_cell.set!(side, new_cell)
267
+ path.push(curr_cell)
268
+ end
269
+ curr_cell = new_cell
270
+ head ||= new_cell
271
+
272
+ else
273
+ duplicate = cell.nil? ? nil : cell.dup_cond(substitutions)
274
+ curr_cell.set!(side, duplicate)
275
+ curr_cell = path.pop if side == :cdr
276
+ end
277
+ end
278
+
279
+ head
280
+ end
281
+
282
+ protected
283
+
284
+ def pair_to_s
285
+ result = +car.to_s
286
+ if cdr
287
+ if cdr.kind_of?(ConsCell)
288
+ result << " #{cdr.pair_to_s}" unless cdr.null?
289
+ else
290
+ result << " . #{cdr}"
291
+ end
292
+ end
293
+
294
+ result
295
+ end
296
+ end # class
297
+
298
+ # Constant set to a null (empty) list.
299
+ NullList = ConsCell.null.freeze
300
+ end # module
301
+ end # module