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
@@ -1,3 +1,55 @@
1
+ ## [0.3.02] - 2020-12-19
2
+ - `mini_kraken` now implements `conso` relation.
3
+
4
+ ### CHANGED
5
+ - Method `Context#build_solution` old source code (redundant) removed.
6
+ - File `README.md` minor changes.
7
+
8
+ ### FIXED
9
+ - Method `ConsCell#expand` pushed a node in the stack even if it was fully visited.
10
+
11
+ ## [0.3.01] - 2020-12-17
12
+ - Minor: updated Rubocop config file `.rubocop.yml`
13
+
14
+ ### CHANGED
15
+ - Config file `.rubocop.yml` updated to incorporate the cops from Rubocop 1.6.1
16
+ - Several files re-styled to please Rubocop
17
+ - `README.md` Checkbox for `cdro` is now ticked
18
+
19
+ ## [0.3.00] - 2020-12-16
20
+ - Version number bump because this is a vast code rewrite
21
+
22
+ ### CHANGED
23
+ - Most classes have been reworked
24
+ - Relation `Equals` has been renamed to `Unify`
25
+
26
+ ## [0.2.04] - 2020-09-02
27
+ - Intermediate version before vast code rework.
28
+
29
+ ### CHANGED
30
+ - Classes `KBoolean`, `KSymbol`, `KInteger` moved to namespace `Atomic`
31
+ - Classes `ConsCell`, `ConsCellVisitor` moved to namespace `Composite`
32
+ - Class `Variable` renamed to `LogVar`
33
+ - Class `VariableRef` renamed to `LogVarRef`
34
+
35
+ ## [0.2.03] - 2020-09-02
36
+ - The DSL (Domain Specific Language) supports the `caro` relation & passes frames up to 2-8 from Chapter 2.
37
+
38
+ ### NEW
39
+ - Class `ConsCellVisitor`. Its method `df_visitor` builds a Fiber that walks over a ConsCell (list/graph).
40
+ - Method `Outcome#failure?`
41
+ - Method `Outcome#prune!` for removing associations of transient variables.
42
+ - Method `VariableRef#to_s` for providing a text representation of a variable reference
43
+ - Method `Vocabulary#prune` for removing associations of transient variables.
44
+ - Class `FreshEnvFactory` as its name implies, is used to build `FreshEnv` instances.
45
+
46
+ ### CHANGED
47
+ - Method `Outcome#successful?` renamed to `Outcome#success?`
48
+
49
+ ### FIXED
50
+ - Method `Equals#solver_for` now prunes associations of transient variables.
51
+ - Method `Equals#unify_composite_terms` now copes with Conscell vs. VariableRef unification.
52
+
1
53
  ## [0.2.02] - 2020-08-08
2
54
  - The DSL (Domain Specific Language) now supports `conde` and passes all examples from Chapter 1.
3
55
 
data/README.md CHANGED
@@ -4,10 +4,9 @@
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
 
@@ -15,24 +14,25 @@ ISBN: 9780262535519, (2018), MIT Press.
15
14
  - Pure Ruby implementation, not a port from another language
16
15
  - Object-Oriented design
17
16
  - No runtime dependencies
17
+ - Test suite patterned on examples from the reference book.
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
- - [X] defrel
26
+ - [X] defrel
27
+ - [X] caro
28
+ - [X] cdro
29
+ - [X] conso
27
30
 
28
31
  ### TODO
29
32
 
30
33
  - [ ] Occurs check
31
34
 
32
- List-centric relations from Chapter 2
33
- - [ ] caro
34
- - [ ] cdro
35
- - [ ] conso
35
+ Pair-centric relations from Chapter 2
36
36
  - [ ] nullo
37
37
  - [ ] pairo
38
38
  - [ ] singletono
@@ -65,7 +65,7 @@ require 'mini_kraken' # Load MiniKraken library
65
65
 
66
66
  extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
67
67
 
68
- result = run_star('q', equals(q, :pea))
68
+ result = run_star('q', unify(q, :pea))
69
69
  puts result # => (:pea)
70
70
  ```
71
71
 
@@ -79,11 +79,11 @@ and satisfying one or more goals to the `run_star method.
79
79
  In our example, the `run_star` method instructs `MiniKraken` to find all solutions,
80
80
  knowing that each successful solution:
81
81
  - binds a value to the provided variable `q` and
82
- - meets the goal `equals(q, :pea)`.
82
+ - meets the goal `unify(q, :pea)`.
83
83
 
84
- The goal `equals(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
84
+ The goal `unify(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
85
85
  not yet bound to a value) and will be bound to the symbol `:pea` as a side effect
86
- of the goal `equals`.
86
+ of the goal `unify`.
87
87
 
88
88
  So the above program succeeds and the only found solution is obtained by binding
89
89
  the variable `q` to the value :pea. Hence the result of the `puts` method.
@@ -97,11 +97,11 @@ So the above program succeeds and the only found solution is obtained by binding
97
97
  extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
98
98
 
99
99
  # Following miniKanren program fails
100
- result = run_star('q', [equals(q, :pea), equals(q, :pod)])
100
+ result = run_star('q', [unify(q, :pea), unify(q, :pod)])
101
101
  puts result # => ()
102
102
  ```
103
103
  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.
104
+ The program fails to find a solution since it is not possible to satisfy the two `unify` goals simultaneously.
105
105
  In case of failure, the `run_star` returns an empty list represented as `()` in the output.
106
106
 
107
107
 
@@ -111,7 +111,7 @@ In case of failure, the `run_star` returns an empty list represented as `()` in
111
111
  ```ruby
112
112
  # In this example and following, one assumes that DSL is loaded as shown in Example 1
113
113
 
114
- result = run_star(['x', 'y'], [equals(:hello, x), equals(y, :world)])
114
+ result = run_star(['x', 'y'], [unify(:hello, x), unify(y, :world)])
115
115
  puts result # => ((:hello :world))
116
116
  ```
117
117
 
@@ -122,8 +122,8 @@ This time, `run_star` takes two logical variables -`x` and `y`- and successfully
122
122
  ```ruby
123
123
  result = run_star(['x', 'y'],
124
124
  [
125
- disj2(equals(x, :blue), equals(x, :red)),
126
- disj2(equals(y, :sea), equals(:mountain, y))
125
+ disj2(unify(x, :blue), unify(x, :red)),
126
+ disj2(unify(y, :sea), unify(:mountain, y))
127
127
  ])
128
128
  puts result # => ((:blue :sea) (:blue :mountain) (:red :sea) (:red :mountain))
129
129
  ```
@@ -12,4 +12,3 @@ module MiniKraken
12
12
  end
13
13
 
14
14
  # End of file
15
-
@@ -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,299 @@
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 = nil)
56
+ super()
57
+ @car = obj1
58
+ if obj2.kind_of?(ConsCell) && obj2.null?
59
+ @cdr = nil
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
+ def children
72
+ [car, cdr]
73
+ end
74
+
75
+ # Return true if it is an empty list, otherwise false.
76
+ # A list is empty, when both car and cdr fields are nil.
77
+ # @return [Boolean]
78
+ def null?
79
+ car.nil? && cdr.nil?
80
+ end
81
+
82
+ # Is the receiver an unbound variable?
83
+ # By definition, a composite isn't a variable.
84
+ # @param _ctx [Core::Context]
85
+ # @return [FalseClass]
86
+ def unbound?(_ctx)
87
+ false
88
+ end
89
+
90
+ # Does the composite have a variable that is itself floating?
91
+ # @return [Boolean]
92
+ def floating?(ctx)
93
+ !pinned?(ctx)
94
+ end
95
+
96
+ # Does the composite have a definite value?
97
+ # @return [Boolean]
98
+ def pinned?(ctx)
99
+ @pinned_car ||= car.nil? || car.pinned?(ctx)
100
+ @pinned_cdr ||= cdr.nil? || cdr.pinned?(ctx)
101
+
102
+ @pinned_car && @pinned_cdr
103
+ end
104
+
105
+ # Return true if car and cdr fields have the same values as the other
106
+ # ConsCell.
107
+ # @param other [ConsCell]
108
+ # @return [Boolean]
109
+ def ==(other)
110
+ return false unless other.respond_to?(:car)
111
+
112
+ (car == other.car) && (cdr == other.cdr)
113
+ end
114
+
115
+ # Test for type and data value equality.
116
+ # @param other [ConsCell]
117
+ # @return [Boolean]
118
+ def eql?(other)
119
+ (self.class == other.class) && car.eql?(other.car) && cdr.eql?(other.cdr)
120
+ end
121
+
122
+ # Return a data object that is a copy of the ConsCell
123
+ # @param anEnv [Core::Environment]
124
+ # @return [ConsCell]
125
+ def quote(anEnv)
126
+ return self if null?
127
+
128
+ new_car = car.nil? ? nil : car.quote(anEnv)
129
+ new_cdr = cdr.nil? ? nil : cdr.quote(anEnv)
130
+ ConsCell.new(new_car, new_cdr)
131
+ end
132
+
133
+ # Use the list notation from Lisp as a text representation.
134
+ # @return [String]
135
+ def to_s
136
+ return '()' if null?
137
+
138
+ "(#{pair_to_s})"
139
+ end
140
+
141
+ # Return the list of variable (i_names) that this term depends on.
142
+ # For a variable reference, it will return the i_names of its variable
143
+ # @param ctx [Core::Context]
144
+ # @return [Set<String>] A set of i_names
145
+ def dependencies(ctx)
146
+ deps = []
147
+ visitor = ConsCellVisitor.df_visitor(self)
148
+ skip_children = false
149
+ loop do
150
+ side, cell = visitor.resume(skip_children)
151
+ if cell.kind_of?(Core::LogVarRef)
152
+ deps << ctx.lookup(cell.name).i_name
153
+ skip_children = true
154
+ else
155
+ skip_children = false
156
+ end
157
+ break if side == :stop
158
+ end
159
+
160
+ Set.new(deps)
161
+ end
162
+
163
+ # @param ctx [Core::Context]
164
+ # @param theSubstitutions [Hash{String => Association}]
165
+ def expand(ctx, theSubstitutions)
166
+ head = curr_cell = nil
167
+ path = []
168
+
169
+ visitor = ConsCellVisitor.df_visitor(self) # Breadth-first!
170
+ skip_children = false
171
+
172
+ loop do
173
+ side, cell = visitor.resume(skip_children)
174
+ # next if cell == self
175
+ break if side == :stop
176
+
177
+ case cell
178
+ when ConsCell
179
+ new_cell = ConsCell.null
180
+ if curr_cell
181
+ curr_cell.set!(side, new_cell)
182
+ path.push(curr_cell) unless side == :cdr
183
+ end
184
+ curr_cell = new_cell
185
+ head ||= new_cell
186
+
187
+ when Core::LogVarRef
188
+ # Is this robust?
189
+ if cell.i_name
190
+ i_name = cell.i_name
191
+ else
192
+ i_name = ctx.symbol_table.lookup(cell.name).i_name
193
+ end
194
+ expanded = ctx.expand_value_of(i_name, theSubstitutions)
195
+ curr_cell.set!(side, expanded)
196
+ curr_cell = path.pop if side == :cdr
197
+
198
+ else
199
+ curr_cell.set!(side, cell)
200
+ curr_cell = path.pop if side == :cdr
201
+ end
202
+ end
203
+
204
+ head
205
+ end
206
+
207
+
208
+ # @param ctx [Core::Context]
209
+ # @param theSubstitutions [Hash{String => Association}]
210
+
211
+
212
+ # File 'lib/mini_kraken/atomic/atomic_term.rb', line 91
213
+ # Make a copy of self with all the variable reference being replaced
214
+ # by the corresponding value in the Hash.
215
+ # @param substitutions [Hash {String => Term}]
216
+ # @return [ConsCell]
217
+ def dup_cond(substitutions)
218
+ head = curr_cell = nil
219
+ path = []
220
+
221
+ visitor = ConsCellVisitor.df_visitor(self) # Breadth-first!
222
+ skip_children = false
223
+
224
+ loop do
225
+ side, cell = visitor.resume(skip_children)
226
+ # next if cell == self
227
+ break if side == :stop
228
+
229
+ if cell.kind_of?(ConsCell)
230
+ new_cell = ConsCell.null
231
+ if curr_cell
232
+ curr_cell.set!(side, new_cell)
233
+ path.push(curr_cell)
234
+ end
235
+ curr_cell = new_cell
236
+ head ||= new_cell
237
+
238
+ else
239
+ duplicate = cell.nil? ? nil : cell.dup_cond(substitutions)
240
+ curr_cell.set!(side, duplicate)
241
+ curr_cell = path.pop if side == :cdr
242
+ end
243
+ end
244
+
245
+ head
246
+ end
247
+
248
+ # Set one element of the pair
249
+ # @param member [Symbol]
250
+ # @param element [Term]
251
+ def set!(member, element)
252
+ case member
253
+ when :car
254
+ set_car!(element)
255
+ when :cdr
256
+ @pinned_cdr = nil
257
+ @cdr = element
258
+ else
259
+ raise StandardError, "Undefined cons cell member #{member}"
260
+ end
261
+ end
262
+
263
+ # Change the car of ConsCell to 'element'.
264
+ # Analogue of set-car! procedure in Scheme.
265
+ # @param element [Term]
266
+ def set_car!(element)
267
+ @pinned_car = nil # To force re-evaluation
268
+ @car = element
269
+ end
270
+
271
+ # Change the cdr of ConsCell to 'element'.
272
+ # Analogue of set-cdr! procedure in Scheme.
273
+ # @param element [Term]
274
+ def set_cdr!(element)
275
+ @pinned_cdr = nil # To force re-evaluation
276
+ @cdr = (element.kind_of?(ConsCell) && element.null?) ? nil : element
277
+ end
278
+
279
+ protected
280
+
281
+ def pair_to_s
282
+ result = +car.to_s
283
+ if cdr
284
+ result << ' '
285
+ if cdr.kind_of?(ConsCell)
286
+ result << cdr.pair_to_s
287
+ else
288
+ result << ". #{cdr}"
289
+ end
290
+ end
291
+
292
+ result
293
+ end
294
+ end # class
295
+
296
+ # Constant set to a null (empty) list.
297
+ NullList = ConsCell.null.freeze
298
+ end # module
299
+ end # module