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.
- checksums.yaml +4 -4
- data/.rubocop.yml +378 -333
- data/CHANGELOG.md +48 -0
- data/README.md +29 -21
- data/lib/mini_kraken/atomic/all_atomic.rb +5 -0
- data/lib/mini_kraken/atomic/atomic_term.rb +96 -0
- data/lib/mini_kraken/atomic/k_boolean.rb +42 -0
- data/lib/mini_kraken/{core → atomic}/k_integer.rb +2 -5
- data/lib/mini_kraken/atomic/k_string.rb +17 -0
- data/lib/mini_kraken/{core → atomic}/k_symbol.rb +4 -8
- data/lib/mini_kraken/composite/all_composite.rb +4 -0
- data/lib/mini_kraken/composite/composite_term.rb +27 -0
- data/lib/mini_kraken/composite/cons_cell.rb +301 -0
- data/lib/mini_kraken/composite/cons_cell_visitor.rb +50 -0
- data/lib/mini_kraken/composite/list.rb +32 -0
- data/lib/mini_kraken/core/all_core.rb +8 -0
- data/lib/mini_kraken/core/any_value.rb +31 -7
- data/lib/mini_kraken/core/arity.rb +69 -0
- data/lib/mini_kraken/core/association.rb +29 -4
- data/lib/mini_kraken/core/association_copy.rb +50 -0
- data/lib/mini_kraken/core/base_term.rb +13 -0
- data/lib/mini_kraken/core/blackboard.rb +315 -0
- data/lib/mini_kraken/core/bookmark.rb +46 -0
- data/lib/mini_kraken/core/context.rb +492 -0
- data/lib/mini_kraken/core/duck_fiber.rb +21 -19
- data/lib/mini_kraken/core/entry.rb +40 -0
- data/lib/mini_kraken/core/fail.rb +20 -18
- data/lib/mini_kraken/core/fusion.rb +29 -0
- data/lib/mini_kraken/core/goal.rb +20 -29
- data/lib/mini_kraken/core/log_var.rb +22 -0
- data/lib/mini_kraken/core/log_var_ref.rb +108 -0
- data/lib/mini_kraken/core/nullary_relation.rb +2 -9
- data/lib/mini_kraken/core/parametrized_term.rb +68 -0
- data/lib/mini_kraken/core/relation.rb +14 -28
- data/lib/mini_kraken/core/scope.rb +67 -0
- data/lib/mini_kraken/core/solver_adapter.rb +58 -0
- data/lib/mini_kraken/core/specification.rb +48 -0
- data/lib/mini_kraken/core/succeed.rb +21 -17
- data/lib/mini_kraken/core/symbol_table.rb +137 -0
- data/lib/mini_kraken/core/term.rb +15 -4
- data/lib/mini_kraken/glue/dsl.rb +44 -88
- data/lib/mini_kraken/glue/run_star_expression.rb +28 -30
- data/lib/mini_kraken/rela/all_rela.rb +8 -0
- data/lib/mini_kraken/rela/binary_relation.rb +30 -0
- data/lib/mini_kraken/rela/conde.rb +143 -0
- data/lib/mini_kraken/rela/conj2.rb +65 -0
- data/lib/mini_kraken/rela/def_relation.rb +93 -0
- data/lib/mini_kraken/rela/disj2.rb +70 -0
- data/lib/mini_kraken/rela/fresh.rb +98 -0
- data/lib/mini_kraken/{core → rela}/goal_relation.rb +7 -9
- data/lib/mini_kraken/rela/unify.rb +265 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/mini_kraken.gemspec +2 -2
- data/spec/.rubocop.yml +1 -1
- data/spec/atomic/atomic_term_spec.rb +98 -0
- data/spec/{core → atomic}/k_boolean_spec.rb +19 -34
- data/spec/{core → atomic}/k_symbol_spec.rb +3 -16
- data/spec/composite/cons_cell_spec.rb +225 -0
- data/spec/{core → composite}/cons_cell_visitor_spec.rb +36 -20
- data/spec/composite/list_spec.rb +50 -0
- data/spec/core/any_value_spec.rb +52 -0
- data/spec/core/arity_spec.rb +92 -0
- data/spec/core/association_copy_spec.rb +69 -0
- data/spec/core/association_spec.rb +31 -4
- data/spec/core/blackboard_spec.rb +287 -0
- data/spec/core/bookmark_spec.rb +40 -0
- data/spec/core/context_spec.rb +245 -0
- data/spec/core/core_spec.rb +40 -0
- data/spec/core/duck_fiber_spec.rb +16 -46
- data/spec/core/fail_spec.rb +5 -6
- data/spec/core/goal_spec.rb +22 -12
- data/spec/core/log_var_ref_spec.rb +105 -0
- data/spec/core/log_var_spec.rb +64 -0
- data/spec/core/nullary_relation_spec.rb +33 -0
- data/spec/core/parametrized_tem_spec.rb +39 -0
- data/spec/core/relation_spec.rb +33 -0
- data/spec/core/scope_spec.rb +73 -0
- data/spec/core/solver_adapter_spec.rb +70 -0
- data/spec/core/specification_spec.rb +43 -0
- data/spec/core/succeed_spec.rb +5 -5
- data/spec/core/symbol_table_spec.rb +142 -0
- data/spec/glue/dsl_chap1_spec.rb +88 -144
- data/spec/glue/dsl_chap2_spec.rb +454 -19
- data/spec/glue/run_star_expression_spec.rb +81 -906
- data/spec/rela/conde_spec.rb +153 -0
- data/spec/rela/conj2_spec.rb +123 -0
- data/spec/rela/def_relation_spec.rb +119 -0
- data/spec/rela/disj2_spec.rb +117 -0
- data/spec/rela/fresh_spec.rb +147 -0
- data/spec/rela/unify_spec.rb +369 -0
- data/spec/support/factory_atomic.rb +29 -0
- data/spec/support/factory_composite.rb +21 -0
- data/spec/support/factory_methods.rb +11 -26
- metadata +98 -70
- data/lib/mini_kraken/core/association_walker.rb +0 -183
- data/lib/mini_kraken/core/atomic_term.rb +0 -67
- data/lib/mini_kraken/core/base_arg.rb +0 -10
- data/lib/mini_kraken/core/binary_relation.rb +0 -63
- data/lib/mini_kraken/core/composite_goal.rb +0 -46
- data/lib/mini_kraken/core/composite_term.rb +0 -41
- data/lib/mini_kraken/core/conde.rb +0 -143
- data/lib/mini_kraken/core/conj2.rb +0 -79
- data/lib/mini_kraken/core/cons_cell.rb +0 -82
- data/lib/mini_kraken/core/cons_cell_visitor.rb +0 -102
- data/lib/mini_kraken/core/def_relation.rb +0 -53
- data/lib/mini_kraken/core/designation.rb +0 -55
- data/lib/mini_kraken/core/disj2.rb +0 -72
- data/lib/mini_kraken/core/environment.rb +0 -73
- data/lib/mini_kraken/core/equals.rb +0 -193
- data/lib/mini_kraken/core/formal_arg.rb +0 -22
- data/lib/mini_kraken/core/formal_ref.rb +0 -25
- data/lib/mini_kraken/core/freshness.rb +0 -45
- data/lib/mini_kraken/core/goal_arg.rb +0 -12
- data/lib/mini_kraken/core/goal_template.rb +0 -102
- data/lib/mini_kraken/core/k_boolean.rb +0 -35
- data/lib/mini_kraken/core/outcome.rb +0 -63
- data/lib/mini_kraken/core/variable.rb +0 -41
- data/lib/mini_kraken/core/variable_ref.rb +0 -84
- data/lib/mini_kraken/core/vocabulary.rb +0 -446
- data/lib/mini_kraken/glue/fresh_env.rb +0 -103
- data/lib/mini_kraken/glue/fresh_env_factory.rb +0 -83
- data/spec/core/association_walker_spec.rb +0 -192
- data/spec/core/conde_spec.rb +0 -147
- data/spec/core/conj2_spec.rb +0 -114
- data/spec/core/cons_cell_spec.rb +0 -107
- data/spec/core/def_relation_spec.rb +0 -97
- data/spec/core/disj2_spec.rb +0 -99
- data/spec/core/environment_spec.rb +0 -142
- data/spec/core/equals_spec.rb +0 -317
- data/spec/core/goal_template_spec.rb +0 -74
- data/spec/core/outcome_spec.rb +0 -56
- data/spec/core/variable_ref_spec.rb +0 -30
- data/spec/core/variable_spec.rb +0 -35
- data/spec/core/vocabulary_spec.rb +0 -219
- data/spec/glue/fresh_env_factory_spec.rb +0 -97
- data/spec/glue/fresh_env_spec.rb +0 -62
data/CHANGELOG.md
CHANGED
@@ -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
|
[](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
|
-
|
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
|
-
|
34
|
-
- [ ]
|
35
|
-
|
36
|
-
-
|
37
|
-
- [ ]
|
38
|
-
- [ ]
|
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',
|
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 `
|
90
|
+
- meets the goal `unify(q, :pea)`.
|
83
91
|
|
84
|
-
The goal `
|
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 `
|
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', [
|
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 `
|
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'], [
|
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(
|
126
|
-
disj2(
|
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,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
|
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
|
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,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
|