mini_kraken 0.1.09 → 0.2.00
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/CHANGELOG.md +56 -0
- data/README.md +96 -8
- data/lib/mini_kraken.rb +8 -1
- data/lib/mini_kraken/core/any_value.rb +5 -1
- data/lib/mini_kraken/core/association_walker.rb +1 -1
- data/lib/mini_kraken/core/base_arg.rb +10 -0
- data/lib/mini_kraken/core/conde.rb +143 -0
- data/lib/mini_kraken/core/conj2.rb +4 -1
- data/lib/mini_kraken/core/cons_cell.rb +28 -1
- data/lib/mini_kraken/core/def_relation.rb +49 -0
- data/lib/mini_kraken/core/disj2.rb +1 -0
- data/lib/mini_kraken/core/equals.rb +15 -11
- data/lib/mini_kraken/core/formal_arg.rb +22 -0
- data/lib/mini_kraken/core/formal_ref.rb +24 -0
- data/lib/mini_kraken/core/goal.rb +11 -4
- data/lib/mini_kraken/core/goal_arg.rb +5 -3
- data/lib/mini_kraken/core/goal_template.rb +60 -0
- data/lib/mini_kraken/core/k_boolean.rb +31 -0
- data/lib/mini_kraken/core/k_symbol.rb +11 -0
- data/lib/mini_kraken/core/outcome.rb +14 -0
- data/lib/mini_kraken/core/relation.rb +7 -0
- data/lib/mini_kraken/core/variable.rb +10 -4
- data/lib/mini_kraken/core/vocabulary.rb +18 -1
- data/lib/mini_kraken/glue/dsl.rb +104 -0
- data/lib/mini_kraken/glue/fresh_env.rb +6 -0
- data/lib/mini_kraken/glue/run_star_expression.rb +1 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conde_spec.rb +147 -0
- data/spec/core/cons_cell_spec.rb +35 -0
- data/spec/core/def_relation_spec.rb +96 -0
- data/spec/core/equals_spec.rb +3 -3
- data/spec/core/goal_template_spec.rb +74 -0
- data/spec/core/k_boolean_spec.rb +107 -0
- data/spec/core/k_symbol_spec.rb +4 -0
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +6 -0
- data/spec/glue/dsl_chap1_spec.rb +498 -0
- data/spec/glue/run_star_expression_spec.rb +341 -7
- data/spec/support/factory_methods.rb +15 -0
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bbfe3d58d4c7271a5c8a54486a743c51d6fddcb3b068095cad44cacad575c83
|
|
4
|
+
data.tar.gz: 012b412a95752592b5be70859e00d94b379e2c4c564d37d932a300a03cbc9716
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 952d93b01de392c7168e7cfb4a3cc911e4d1b53be11a4a10d67012bb4ab4a8f69aa42f41bf13a756f89a6d09eb775a5594174d73334c57c2230039b7991e89fd
|
|
7
|
+
data.tar.gz: a60adbd5da627495badab63de355336f131a5e599c40226931366a7c1b4a349b2542ccb55cf30ef8e60990341c1f5d8547804b1552fbbbc504bd1bed40a36d9e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
## [0.2.00] - 2020-07-12
|
|
2
|
+
- First release of DSL (Domain Specific Language)
|
|
3
|
+
- Fix defect for fused variables that remain fresh
|
|
4
|
+
|
|
5
|
+
### NEW
|
|
6
|
+
- Mix-in module `Glue::DSL` hosting methods for implementing the DSL.
|
|
7
|
+
- Method `ConsCell#to_s` uses the Lisp convention for representing lists.
|
|
8
|
+
|
|
9
|
+
### CHANGED
|
|
10
|
+
- File `README.md` Added a couple of examples of DSL use.
|
|
11
|
+
- Method `AnyValue#==` can compare with symbols with format '_' + integer literal (e.g. :_0).
|
|
12
|
+
|
|
13
|
+
## [0.1.13] - 2020-07-01
|
|
14
|
+
- Cover all frames from Chapter One of "Reasoned Scheme" book.
|
|
15
|
+
- Fix defect for fused variables that remain fresh
|
|
16
|
+
|
|
17
|
+
### CHANGED
|
|
18
|
+
- Method `Variable#quote` now takes into account of cases when variables are fused.
|
|
19
|
+
- Method `Vocabulary#names_fused` now copes with cases where no variable with given name can be found.
|
|
20
|
+
|
|
21
|
+
## [0.1.12] - 2020-06-29
|
|
22
|
+
- Supports `conde`, that is, a relation that can take an arbitrary number of arguments.
|
|
23
|
+
- Cover all frames but one from Chapter One of "Reasoned Scheme" book.
|
|
24
|
+
|
|
25
|
+
### New
|
|
26
|
+
- Class `Conde` a relation that succeeds for each of its successful arguments.
|
|
27
|
+
|
|
28
|
+
### CHANGED
|
|
29
|
+
- Method `Goal#validated_actuals` add into account polyadic relations (= relations with arbitrary number of arguments)
|
|
30
|
+
|
|
31
|
+
## [0.1.11] - 2020-06-25
|
|
32
|
+
- Supports `defrel`, that is, the capability to define new relations by combining other relations.
|
|
33
|
+
- Covers frames from "The Reasoned Scheme" book up to frame [1:87]
|
|
34
|
+
|
|
35
|
+
### New
|
|
36
|
+
- Class `BaseArg` a generalization of goal or goal template argument.
|
|
37
|
+
- Class `DefRelation` A specialization of `Relation` class aimed for user-defined relation.
|
|
38
|
+
- Class `FormalArg` to represent goal template argument(s).
|
|
39
|
+
- Class `FormalRef` an allusion to a formal argument in a goal template.
|
|
40
|
+
- Class `GoalTemplate` a representation of a goal parametrized with formal arguments.
|
|
41
|
+
- Class `KBoolean` a MiniKraken representation of a boolean value.
|
|
42
|
+
|
|
43
|
+
### CHANGED
|
|
44
|
+
- File `README.md` minor change: added more TODO's.
|
|
45
|
+
|
|
46
|
+
## [0.1.10] - 2020-06-13
|
|
47
|
+
- Supports frames from "The Reasoned Scheme" book up to frame [1:81]
|
|
48
|
+
|
|
49
|
+
### New
|
|
50
|
+
- Factory methods `Outcome#failure`, `Outcome#success`
|
|
51
|
+
- Method `Vocabulary#inspect`
|
|
52
|
+
- File `outcome_spec.rb`
|
|
53
|
+
|
|
54
|
+
### FIXED
|
|
55
|
+
- `Conj2#conjunction` vocabulary wasn't cleared when outcome2 was nil.
|
|
56
|
+
|
|
1
57
|
## [0.1.09] - 2020-06-06
|
|
2
58
|
- Supports frames from "The Reasoned Scheme" book up to frame [1:76]
|
|
3
59
|
|
data/README.md
CHANGED
|
@@ -4,24 +4,39 @@
|
|
|
4
4
|
[](https://github.com/famished-tiger/mini_kraken/blob/master/LICENSE.txt)
|
|
5
5
|
|
|
6
6
|
### What is __mini_kraken__ ?
|
|
7
|
-
|
|
7
|
+
A library containing an implementation of the [miniKanren](http://minikanren.org/)
|
|
8
|
+
relational programming language in Ruby.
|
|
8
9
|
*miniKanren* is a small language for relational (logic) programming.
|
|
9
|
-
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
|
10
|
+
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
|
10
11
|
Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
|
|
11
12
|
ISBN: 9780262535519, (2018), MIT Press.
|
|
12
13
|
|
|
13
14
|
### Features
|
|
15
|
+
- Pure Ruby implementation, not a port from another language
|
|
16
|
+
- Object-Oriented design
|
|
17
|
+
- No runtime dependencies
|
|
18
|
+
|
|
19
|
+
### miniKanren Features
|
|
14
20
|
- [X] ==
|
|
15
21
|
- [X] run\*
|
|
16
|
-
- [X] fresh
|
|
22
|
+
- [X] fresh
|
|
23
|
+
- [X] conde
|
|
17
24
|
- [X] conj2
|
|
18
|
-
- [X] disj2
|
|
25
|
+
- [X] disj2
|
|
26
|
+
- [X] defrel
|
|
19
27
|
|
|
20
28
|
### TODO
|
|
21
|
-
|
|
22
|
-
- [ ] conde
|
|
29
|
+
|
|
23
30
|
- [ ] Occurs check
|
|
24
31
|
|
|
32
|
+
List-centric relations from Chapter 2
|
|
33
|
+
- [ ] caro
|
|
34
|
+
- [ ] cdro
|
|
35
|
+
- [ ] conso
|
|
36
|
+
- [ ] nullo
|
|
37
|
+
- [ ] pairo
|
|
38
|
+
- [ ] singletono
|
|
39
|
+
|
|
25
40
|
## Installation
|
|
26
41
|
|
|
27
42
|
Add this line to your application's Gemfile:
|
|
@@ -38,9 +53,82 @@ Or install it yourself as:
|
|
|
38
53
|
|
|
39
54
|
$ gem install mini_kraken
|
|
40
55
|
|
|
41
|
-
##
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
The following __MiniKraken__ examples use its DSL (Domain Specific Language).
|
|
59
|
+
|
|
60
|
+
### Example 1
|
|
61
|
+
Let's first begin with a rather simplistic example.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
require 'mini_kraken' # Load MiniKraken library
|
|
65
|
+
|
|
66
|
+
extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
|
|
67
|
+
|
|
68
|
+
result = run_star('q', equals(q, :pea))
|
|
69
|
+
puts result # => (:pea)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The two first lines in the above code snippet are pretty standard:
|
|
73
|
+
- The first line loads the `mini_kraken` library.
|
|
74
|
+
- The second line add the DSL methods to the current object.
|
|
75
|
+
|
|
76
|
+
The next line constitutes a trivial `miniKanren` program.
|
|
77
|
+
The aim of a `miniKanren` program is to find one or more solutions involving provided variable(s)
|
|
78
|
+
and satisfying one or more goals.
|
|
79
|
+
In our example, the `run_star` method instructs `MiniKraken` to find all solutions,
|
|
80
|
+
knowing that each successful solution:
|
|
81
|
+
- binds a value to the provided variable `q` and
|
|
82
|
+
- meets the goal `equals(q, :pea)`.
|
|
83
|
+
|
|
84
|
+
The goal `equals(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
|
|
85
|
+
not yet bound to a value) and will be bound to the symbol `:pea` as a side effect
|
|
86
|
+
of the goal `equals`.
|
|
87
|
+
|
|
88
|
+
So the above program succeeds and the only found solution is obtained by binding
|
|
89
|
+
the variable `q` to the value :pea. Hence the result of the `puts` method.
|
|
90
|
+
|
|
91
|
+
### Example 2
|
|
92
|
+
The next example illustrates the behavior of a failing `miniKanren` program.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
require 'mini_kraken' # Load MiniKraken library
|
|
96
|
+
|
|
97
|
+
extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
|
|
98
|
+
|
|
99
|
+
# Following miniKanren program fails
|
|
100
|
+
result = run_star('q', [equals(q, :pea), equals(q, :pod)])
|
|
101
|
+
puts result # => ()
|
|
102
|
+
```
|
|
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. In that case, the `run_star` return an empty list represented as `()` in the output.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Example 3
|
|
108
|
+
The next example shows the use two logical variables.
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
# In this example and following, one assumes that DSL is loaded as shown in Example 1
|
|
112
|
+
|
|
113
|
+
result = run_star(['x', 'y'], [equals(:hello, x), equals(y, :world)])
|
|
114
|
+
puts result # => ((:hello :world))
|
|
115
|
+
```
|
|
42
116
|
|
|
43
|
-
|
|
117
|
+
This time, `run_star` takes two logical variables -`x` and `y`- and successfully finds the solution `x = :hello, y = :world`.
|
|
118
|
+
|
|
119
|
+
### Example 4
|
|
120
|
+
The next example shows the use of `disj2` goals.
|
|
121
|
+
```ruby
|
|
122
|
+
result = run_star(['x', 'y'],
|
|
123
|
+
[
|
|
124
|
+
disj2(equals(x, :blue), equals(x, :red)),
|
|
125
|
+
disj2(equals(y, :sea), equals(:mountain, y))
|
|
126
|
+
])
|
|
127
|
+
puts result # => ((:blue :sea) (:blue :mountain) (:red :sea) (:red :mountain))
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Here, `run_star` takes two logical variables and two `disj2` goals. A `disj2` succeeds if any of its arguments succeeds.
|
|
131
|
+
This program finds four distinct solutions for x, y pairs.
|
|
44
132
|
|
|
45
133
|
## Development
|
|
46
134
|
|
data/lib/mini_kraken.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# This file acts as a jumping-off point for loading dependencies expected
|
|
4
|
+
# for a MiniKraken client.
|
|
5
|
+
|
|
6
|
+
require_relative './mini_kraken/version'
|
|
7
|
+
require_relative './mini_kraken/glue/dsl'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# End of file
|
|
4
11
|
|
|
5
12
|
module MiniKraken
|
|
6
13
|
class Error < StandardError; end
|
|
@@ -12,7 +12,11 @@ module MiniKraken
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def ==(other)
|
|
15
|
-
|
|
15
|
+
if other.is_a?(AnyValue)
|
|
16
|
+
rank == other.rank
|
|
17
|
+
elsif other.id2name =~ /_\d+/
|
|
18
|
+
rank == other.id2name.sub(/_/, '').to_i
|
|
19
|
+
end
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
# Use same text representation as in Reasoned Schemer.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require_relative 'conj2'
|
|
5
|
+
require_relative 'duck_fiber'
|
|
6
|
+
require_relative 'fail'
|
|
7
|
+
require_relative 'goal'
|
|
8
|
+
require_relative 'goal_relation'
|
|
9
|
+
require_relative 'outcome'
|
|
10
|
+
|
|
11
|
+
unless MiniKraken::Core.constants(false).include? :Conde
|
|
12
|
+
module MiniKraken
|
|
13
|
+
module Core
|
|
14
|
+
# A polyadic relation (i.e. it can takes an arbitrary number of argumentt)
|
|
15
|
+
# that behaves as the disjunction of its arguments.
|
|
16
|
+
# It succeeds if at least one of its goal arguments succeeds.
|
|
17
|
+
class Conde < GoalRelation
|
|
18
|
+
include Singleton
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
super('conde', nil)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A relation is polyadic when it accepts an arbitrary number of arguments.
|
|
25
|
+
# @return [TrueClass]
|
|
26
|
+
def polyadic?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param actuals [Array<Term>] A two-elements array
|
|
31
|
+
# @param anEnv [Vocabulary] A vocabulary object
|
|
32
|
+
# @return [Fiber<Outcome>] A Fiber that yields Outcomes objects
|
|
33
|
+
def solver_for(actuals, anEnv)
|
|
34
|
+
args = *validated_args(actuals)
|
|
35
|
+
Fiber.new { cond(args, anEnv) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Yields [Outcome, NilClass] result of the disjunction
|
|
39
|
+
# @param goals [Array<Goal>] Array of goals
|
|
40
|
+
# @param voc [Vocabulary] A vocabulary object
|
|
41
|
+
def cond(goals, voc)
|
|
42
|
+
# require 'debug'
|
|
43
|
+
success = false
|
|
44
|
+
|
|
45
|
+
goals.each do |g|
|
|
46
|
+
fiber = nil
|
|
47
|
+
|
|
48
|
+
case g
|
|
49
|
+
when Core::Goal
|
|
50
|
+
fiber = g.attain(voc)
|
|
51
|
+
when Core::Environment
|
|
52
|
+
fiber = g.attain(voc)
|
|
53
|
+
when Array
|
|
54
|
+
conjunct = conjunction(g)
|
|
55
|
+
fiber = conjunct.attain(voc)
|
|
56
|
+
when Core::ConsCell
|
|
57
|
+
goal_array = to_goal_array(g)
|
|
58
|
+
conjunct = conjunction(goal_array)
|
|
59
|
+
fiber = conjunct.attain(voc)
|
|
60
|
+
end
|
|
61
|
+
loop do
|
|
62
|
+
outcome = fiber.resume
|
|
63
|
+
break unless outcome
|
|
64
|
+
|
|
65
|
+
outcome.parent = voc unless outcome.parent
|
|
66
|
+
if outcome.successful?
|
|
67
|
+
success = true
|
|
68
|
+
Fiber.yield outcome
|
|
69
|
+
outcome.clear
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Fiber.yield Outcome.new(:"#u", voc) unless success
|
|
75
|
+
Fiber.yield nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def validated_args(actuals)
|
|
81
|
+
result = []
|
|
82
|
+
|
|
83
|
+
actuals.each do |arg|
|
|
84
|
+
case arg
|
|
85
|
+
when Core::Goal
|
|
86
|
+
result << arg
|
|
87
|
+
|
|
88
|
+
when Core::Environment
|
|
89
|
+
result << arg
|
|
90
|
+
|
|
91
|
+
when Array
|
|
92
|
+
result << validated_args(arg)
|
|
93
|
+
|
|
94
|
+
else
|
|
95
|
+
prefix = "#{name} expects goal as argument, found a "
|
|
96
|
+
raise StandardError, prefix + "'#{arg.class}'"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
result
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def conjunction(goal_array)
|
|
104
|
+
result = nil
|
|
105
|
+
|
|
106
|
+
loop do
|
|
107
|
+
conjunctions = []
|
|
108
|
+
goal_array.each_slice(2) do |uno_duo|
|
|
109
|
+
if uno_duo.size == 2
|
|
110
|
+
conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
|
|
111
|
+
else
|
|
112
|
+
conjunctions << uno_duo[0]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
if conjunctions.size == 1
|
|
116
|
+
result = conjunctions[0]
|
|
117
|
+
break
|
|
118
|
+
end
|
|
119
|
+
goal_array = conjunctions
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
result
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def to_goal_array(aCons)
|
|
126
|
+
array = []
|
|
127
|
+
curr_node = aCons
|
|
128
|
+
loop do
|
|
129
|
+
array << curr_node.car if curr_node.car.kind_of?(Core::Goal)
|
|
130
|
+
break unless curr_node.cdr
|
|
131
|
+
break unless curr_node.car.kind_of?(Core::Goal)
|
|
132
|
+
|
|
133
|
+
curr_node = curr_node.cdr
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
array
|
|
137
|
+
end
|
|
138
|
+
end # class
|
|
139
|
+
|
|
140
|
+
Conde.instance.freeze
|
|
141
|
+
end # module
|
|
142
|
+
end # module
|
|
143
|
+
end # unless
|
|
@@ -31,6 +31,7 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
|
31
31
|
# @param g2 [Goal] Second goal argument
|
|
32
32
|
# @param voc [Vocabulary] A vocabulary object
|
|
33
33
|
def conjunction(g1, g2, voc)
|
|
34
|
+
# require 'debug'
|
|
34
35
|
if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
|
|
35
36
|
Fiber.yield Outcome.new(:"#u", voc)
|
|
36
37
|
else
|
|
@@ -62,7 +63,9 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
|
62
63
|
else
|
|
63
64
|
Fiber.yield outcome1
|
|
64
65
|
end
|
|
65
|
-
|
|
66
|
+
if outcome1.successful? && (outcome2&.successful? || outcome2.nil?)
|
|
67
|
+
voc.clear
|
|
68
|
+
end
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
71
|
|
|
@@ -11,7 +11,11 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
|
|
|
11
11
|
|
|
12
12
|
def initialize(obj1, obj2 = nil)
|
|
13
13
|
@car = obj1
|
|
14
|
-
|
|
14
|
+
if obj2.kind_of?(ConsCell) && obj2.null?
|
|
15
|
+
@cdr = nil
|
|
16
|
+
else
|
|
17
|
+
@cdr = obj2
|
|
18
|
+
end
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def children
|
|
@@ -42,9 +46,32 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
|
|
|
42
46
|
ConsCell.new(new_car, new_cdr)
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
# Use the list notation from Lisp as a text representation.
|
|
50
|
+
def to_s
|
|
51
|
+
return '()' if null?
|
|
52
|
+
|
|
53
|
+
"(#{pair_to_s})"
|
|
54
|
+
end
|
|
55
|
+
|
|
45
56
|
def append(another)
|
|
46
57
|
@cdr = another
|
|
47
58
|
end
|
|
59
|
+
|
|
60
|
+
protected
|
|
61
|
+
|
|
62
|
+
def pair_to_s
|
|
63
|
+
result = +car.to_s
|
|
64
|
+
if cdr
|
|
65
|
+
result << ' '
|
|
66
|
+
if cdr.kind_of?(ConsCell)
|
|
67
|
+
result << cdr.pair_to_s
|
|
68
|
+
else
|
|
69
|
+
result << ". #{cdr}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
result
|
|
74
|
+
end
|
|
48
75
|
end # class
|
|
49
76
|
|
|
50
77
|
# Constant representing the null (empty) list.
|