hammock-ruby 0.0.1.alpha
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 +7 -0
- data/README.md +132 -0
- data/bin/hammock +55 -0
- data/lib/clojure/core.clj +6872 -0
- data/lib/hammock.rb +0 -0
- data/lib/hammock/aref.rb +57 -0
- data/lib/hammock/array_chunk.rb +49 -0
- data/lib/hammock/atom.rb +50 -0
- data/lib/hammock/block.rb +8 -0
- data/lib/hammock/chunk_buffer.rb +25 -0
- data/lib/hammock/chunked_cons.rb +108 -0
- data/lib/hammock/chunked_seq.rb +97 -0
- data/lib/hammock/compiler.rb +197 -0
- data/lib/hammock/environment.rb +40 -0
- data/lib/hammock/errors.rb +14 -0
- data/lib/hammock/function.rb +187 -0
- data/lib/hammock/ichunked_seq.rb +23 -0
- data/lib/hammock/ideref.rb +5 -0
- data/lib/hammock/ifn.rb +10 -0
- data/lib/hammock/ilookup.rb +6 -0
- data/lib/hammock/interfaces.rb +80 -0
- data/lib/hammock/ipersistent_collection.rb +15 -0
- data/lib/hammock/ireference.rb +15 -0
- data/lib/hammock/iseq.rb +12 -0
- data/lib/hammock/lazy_sequence.rb +82 -0
- data/lib/hammock/lazy_transformer.rb +169 -0
- data/lib/hammock/list.rb +232 -0
- data/lib/hammock/loop_locals.rb +34 -0
- data/lib/hammock/map.rb +205 -0
- data/lib/hammock/meta.rb +12 -0
- data/lib/hammock/multi_method.rb +52 -0
- data/lib/hammock/namespace.rb +185 -0
- data/lib/hammock/reader.rb +570 -0
- data/lib/hammock/recur_locals.rb +16 -0
- data/lib/hammock/reduced.rb +15 -0
- data/lib/hammock/repl.rb +29 -0
- data/lib/hammock/rt.rb +580 -0
- data/lib/hammock/sequence.rb +59 -0
- data/lib/hammock/set.rb +144 -0
- data/lib/hammock/stream.rb +40 -0
- data/lib/hammock/symbol.rb +65 -0
- data/lib/hammock/var.rb +186 -0
- data/lib/hammock/vector.rb +309 -0
- data/lib/hammock/version.rb +3 -0
- data/lib/hammock/volatile.rb +19 -0
- data/spec/examples/data.hmk +4 -0
- data/spec/hammock/reader_spec.rb +242 -0
- data/spec/hammock/rt_spec.rb +10 -0
- data/spec/hammock/sequence_spec.rb +24 -0
- metadata +139 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
require 'hammock/reduced'
|
3
|
+
require 'hammock/meta'
|
4
|
+
require 'hammock/ifn'
|
5
|
+
|
6
|
+
module Hammock
|
7
|
+
class LazyTransformer
|
8
|
+
include Meta
|
9
|
+
|
10
|
+
attr_accessor :rest, :stepper
|
11
|
+
attr_writer :first
|
12
|
+
|
13
|
+
def self.create(xform, coll)
|
14
|
+
new(Stepper.new(xform, RT.iter(coll)))
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
if Stepper === args.first
|
19
|
+
@stepper = args.first
|
20
|
+
@first = nil
|
21
|
+
@rest = nil
|
22
|
+
@meta = nil
|
23
|
+
else
|
24
|
+
@meta, @first, @rest = args
|
25
|
+
@stepper = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_meta(meta)
|
30
|
+
seq
|
31
|
+
self.class.new(meta, first, rest)
|
32
|
+
end
|
33
|
+
|
34
|
+
def stepper
|
35
|
+
@stepper
|
36
|
+
end
|
37
|
+
|
38
|
+
def seq
|
39
|
+
stepper.step(self) unless stepper.nil?
|
40
|
+
|
41
|
+
if @rest.nil?
|
42
|
+
nil
|
43
|
+
else
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def first
|
49
|
+
seq unless stepper.nil?
|
50
|
+
if @rest.nil?
|
51
|
+
nil
|
52
|
+
else
|
53
|
+
@first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def next
|
58
|
+
seq unless stepper.nil?
|
59
|
+
if @rest.nil?
|
60
|
+
nil
|
61
|
+
else
|
62
|
+
@rest.seq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias tail next
|
66
|
+
|
67
|
+
def more
|
68
|
+
seq unless stepper.nil?
|
69
|
+
if @rest.nil?
|
70
|
+
EmptyList.new
|
71
|
+
else
|
72
|
+
@rest.seq
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_a
|
77
|
+
ret = []
|
78
|
+
s = seq
|
79
|
+
until s.nil?
|
80
|
+
ret << s.first
|
81
|
+
s = s.next
|
82
|
+
end
|
83
|
+
ret
|
84
|
+
end
|
85
|
+
|
86
|
+
def count
|
87
|
+
i = 0
|
88
|
+
s = seq
|
89
|
+
until s.nil?
|
90
|
+
i += 0
|
91
|
+
s = s.next
|
92
|
+
end
|
93
|
+
i
|
94
|
+
end
|
95
|
+
|
96
|
+
def realized?
|
97
|
+
stepper.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def empty?
|
101
|
+
seq.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
"(#{to_a.map(&:inspect).join(' ')})"
|
106
|
+
end
|
107
|
+
alias to_s inspect
|
108
|
+
|
109
|
+
|
110
|
+
class Stepper
|
111
|
+
class StepFn
|
112
|
+
include IFn
|
113
|
+
def call(*args)
|
114
|
+
if args.length == 1
|
115
|
+
apply_result(args.first)
|
116
|
+
else
|
117
|
+
apply_result_with_input(*args)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def apply_result_with_input(result, input)
|
122
|
+
lt = result
|
123
|
+
lt.first = input
|
124
|
+
lt.rest = LazyTransformer.new(lt.stepper)
|
125
|
+
lt.stepper = nil
|
126
|
+
lt.rest
|
127
|
+
end
|
128
|
+
|
129
|
+
def apply_result(result)
|
130
|
+
lt = result
|
131
|
+
lt.stepper = nil
|
132
|
+
result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def initialize(xform, iter)
|
137
|
+
@iter = iter
|
138
|
+
@xform = xform.call(StepFn.new)
|
139
|
+
end
|
140
|
+
|
141
|
+
def next?
|
142
|
+
@iter.peek
|
143
|
+
true
|
144
|
+
rescue StopIteration
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
def step(lt)
|
149
|
+
while !lt.stepper.nil? && next?
|
150
|
+
if RT.reduced?(@xform.call(lt, @iter.next))
|
151
|
+
lt.stepper = nil
|
152
|
+
et = lt
|
153
|
+
until et.rest.nil?
|
154
|
+
et = et.rest
|
155
|
+
et.stepper = nil
|
156
|
+
end
|
157
|
+
@xform.call(et)
|
158
|
+
return
|
159
|
+
end
|
160
|
+
end
|
161
|
+
unless lt.stepper.nil?
|
162
|
+
@xform.call(lt)
|
163
|
+
end
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
data/lib/hammock/list.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'hamster/enumerable'
|
4
|
+
require 'hammock/set'
|
5
|
+
require 'hammock/compiler'
|
6
|
+
require 'hammock/ipersistent_collection'
|
7
|
+
require 'hammock/iseq'
|
8
|
+
|
9
|
+
module Hammock
|
10
|
+
module List
|
11
|
+
include IPersistentCollection
|
12
|
+
include Hamster::Enumerable
|
13
|
+
include ISeq
|
14
|
+
|
15
|
+
Undefined = Object.new
|
16
|
+
|
17
|
+
CADR = /^c([ad]+)r$/
|
18
|
+
|
19
|
+
def first; head end
|
20
|
+
def null?; empty? end
|
21
|
+
def rest; tail end
|
22
|
+
|
23
|
+
def size
|
24
|
+
reduce(0) { |memo, item| memo.next }
|
25
|
+
end
|
26
|
+
alias length size
|
27
|
+
alias count size
|
28
|
+
|
29
|
+
def cons(item)
|
30
|
+
Sequence.new(item, self)
|
31
|
+
end
|
32
|
+
alias conj cons
|
33
|
+
|
34
|
+
def each
|
35
|
+
return self unless block_given?
|
36
|
+
list = self
|
37
|
+
while !list.empty?
|
38
|
+
yield(list.head)
|
39
|
+
list = list.tail
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_a
|
44
|
+
ret = []
|
45
|
+
each do |obj|
|
46
|
+
ret << obj
|
47
|
+
end
|
48
|
+
ret
|
49
|
+
end
|
50
|
+
|
51
|
+
def map(&block)
|
52
|
+
return self unless block_given?
|
53
|
+
Stream.new do
|
54
|
+
next self if empty?
|
55
|
+
Sequence.new(yield(head), tail.map(&block))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def append(other)
|
60
|
+
Stream.new do
|
61
|
+
next other if empty?
|
62
|
+
Sequence.new(head, tail.append(other))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias cat append
|
66
|
+
alias concat append
|
67
|
+
alias + append
|
68
|
+
|
69
|
+
def cycle
|
70
|
+
Stream.new do
|
71
|
+
next self if empty?
|
72
|
+
Sequence.new(head, tail.append(self.cycle))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def clear
|
77
|
+
EmptyList.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def join(sep = "")
|
81
|
+
return "" if empty?
|
82
|
+
sep = sep.to_s
|
83
|
+
tail.reduce(head.to_s.dup) { |result, item| result << sep << item.to_s }
|
84
|
+
end
|
85
|
+
|
86
|
+
def last
|
87
|
+
list = self
|
88
|
+
while !list.tail.empty?
|
89
|
+
list = list.tail
|
90
|
+
end
|
91
|
+
list.head
|
92
|
+
end
|
93
|
+
|
94
|
+
def drop(number)
|
95
|
+
Stream.new do
|
96
|
+
list = self
|
97
|
+
while !list.empty? && number > 0
|
98
|
+
number -= 1
|
99
|
+
list = list.tail
|
100
|
+
end
|
101
|
+
list
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def nth(n, default=Undefined)
|
106
|
+
res = drop(n)
|
107
|
+
if res.empty? && default != Undefined
|
108
|
+
default
|
109
|
+
else
|
110
|
+
res.head
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def chunk(number)
|
115
|
+
Stream.new do
|
116
|
+
next self if empty?
|
117
|
+
first, remainder = split_at(number)
|
118
|
+
Sequence.new(first, remainder.chunk(number))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def each_chunk(number, &block)
|
123
|
+
chunk(number).each(&block)
|
124
|
+
end
|
125
|
+
alias each_slice each_chunk
|
126
|
+
|
127
|
+
def flatten
|
128
|
+
Stream.new do
|
129
|
+
next self if empty?
|
130
|
+
next head.append(tail.flatten) if head.is_a?(List)
|
131
|
+
Sequence.new(head, tail.flatten)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def group_by(&block)
|
136
|
+
return group_by { |item| item } unless block_given?
|
137
|
+
reduce(EmptyHash) do |hash, item|
|
138
|
+
key = yield(item)
|
139
|
+
hash.put(key, (hash.get(key) || EmptyList.new).cons(item))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def at(index)
|
144
|
+
drop(index).head
|
145
|
+
end
|
146
|
+
|
147
|
+
def eql?(other)
|
148
|
+
list = self
|
149
|
+
loop do
|
150
|
+
return true if other.equal?(list)
|
151
|
+
return false unless other.is_a?(List)
|
152
|
+
return other.empty? if list.empty?
|
153
|
+
return false if other.empty?
|
154
|
+
return false unless other.head.eql?(list.head)
|
155
|
+
list = list.tail
|
156
|
+
other = other.tail
|
157
|
+
end
|
158
|
+
end
|
159
|
+
alias == eql?
|
160
|
+
|
161
|
+
def hash
|
162
|
+
reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
|
163
|
+
end
|
164
|
+
|
165
|
+
def empty
|
166
|
+
EmptyList.new
|
167
|
+
end
|
168
|
+
|
169
|
+
def dup
|
170
|
+
self
|
171
|
+
end
|
172
|
+
alias clone dup
|
173
|
+
|
174
|
+
def to_list
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
def respond_to?(name, include_private = false)
|
179
|
+
super || CADR === name.to_s
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def method_missing(name, *args, &block)
|
185
|
+
return accessor($1) if CADR === name.to_s
|
186
|
+
super
|
187
|
+
end
|
188
|
+
|
189
|
+
def accessor(sequence)
|
190
|
+
sequence.reverse.each_char.reduce(self) do |memo, char|
|
191
|
+
case char
|
192
|
+
when "a" then memo.head
|
193
|
+
when "d" then memo.tail
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class EmptyList
|
200
|
+
include List
|
201
|
+
include Meta
|
202
|
+
|
203
|
+
def initialize(meta=nil)
|
204
|
+
@meta = meta
|
205
|
+
end
|
206
|
+
|
207
|
+
def head
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def tail
|
212
|
+
self
|
213
|
+
end
|
214
|
+
|
215
|
+
def seq
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
|
219
|
+
def with_meta(meta)
|
220
|
+
self.class.new(meta)
|
221
|
+
end
|
222
|
+
|
223
|
+
def empty?
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
227
|
+
def inspect
|
228
|
+
"()"
|
229
|
+
end
|
230
|
+
alias to_s inspect
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'hammock/map'
|
2
|
+
require 'hammock/vector'
|
3
|
+
|
4
|
+
module Hammock
|
5
|
+
class LoopLocals
|
6
|
+
def self.empty
|
7
|
+
new([], [])
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(names, bindings)
|
11
|
+
unless Hammock::Map === bindings
|
12
|
+
bindings = Hammock::Map.new bindings
|
13
|
+
end
|
14
|
+
unless Hammock::Vector === names
|
15
|
+
names = Hammock::Vector.from_array(names)
|
16
|
+
end
|
17
|
+
@bindings = bindings
|
18
|
+
@names = names
|
19
|
+
end
|
20
|
+
|
21
|
+
def frame
|
22
|
+
@bindings
|
23
|
+
end
|
24
|
+
|
25
|
+
def rebind(recur_locals)
|
26
|
+
bindings = Hammock::Map.from_array @names.to_a.zip(recur_locals.to_a).flatten(1)
|
27
|
+
self.class.new(@names, bindings)
|
28
|
+
end
|
29
|
+
|
30
|
+
def bind(name, val)
|
31
|
+
self.class.new(@names.cons(name), @bindings.assoc(name, val))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/hammock/map.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'hamster/immutable'
|
3
|
+
require 'hamster/trie'
|
4
|
+
|
5
|
+
require 'hammock/ipersistent_collection'
|
6
|
+
require 'hammock/meta'
|
7
|
+
require 'hammock/ifn'
|
8
|
+
require 'hammock/ilookup'
|
9
|
+
require 'hammock/iseq'
|
10
|
+
|
11
|
+
module Hammock
|
12
|
+
class Map
|
13
|
+
include Hamster::Immutable
|
14
|
+
include IPersistentCollection
|
15
|
+
include Meta
|
16
|
+
include IFn
|
17
|
+
include ILookup
|
18
|
+
include ISeq
|
19
|
+
|
20
|
+
Undefined = Object.new
|
21
|
+
|
22
|
+
def self.alloc_from(map, meta=nil)
|
23
|
+
map.send(:transform) { @meta = meta }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create(coll)
|
27
|
+
case coll
|
28
|
+
when Map
|
29
|
+
coll
|
30
|
+
when Hash
|
31
|
+
from_hash(coll)
|
32
|
+
else
|
33
|
+
from_array(coll.to_a)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_hash(hash = {})
|
38
|
+
map = new
|
39
|
+
hash.reduce(map) { |m, (k, v)| m.put(k, v) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.from_array(array, meta=nil)
|
43
|
+
map = new(meta)
|
44
|
+
array.each_slice(2) do |pair|
|
45
|
+
map = map.put(pair.first, pair.last)
|
46
|
+
end
|
47
|
+
map
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.from_pairs(array, meta=nil)
|
51
|
+
map = new(meta)
|
52
|
+
array.each do |pair|
|
53
|
+
map = map.put(pair.first, pair.last)
|
54
|
+
end
|
55
|
+
map
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(meta=nil)
|
59
|
+
@meta = meta
|
60
|
+
@trie = Hamster::EmptyTrie
|
61
|
+
end
|
62
|
+
|
63
|
+
def size
|
64
|
+
@trie.size
|
65
|
+
end
|
66
|
+
alias length size
|
67
|
+
alias count size
|
68
|
+
|
69
|
+
def empty?
|
70
|
+
@trie.empty?
|
71
|
+
end
|
72
|
+
alias null? empty?
|
73
|
+
|
74
|
+
def has_key?(key)
|
75
|
+
@trie.has_key?(key)
|
76
|
+
end
|
77
|
+
alias key? has_key?
|
78
|
+
|
79
|
+
def entry_at(key)
|
80
|
+
@trie.get(key)
|
81
|
+
end
|
82
|
+
|
83
|
+
def get(key)
|
84
|
+
if entry = entry_at(key)
|
85
|
+
entry.value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
alias [] get
|
89
|
+
|
90
|
+
def fetch(key, default = Undefined)
|
91
|
+
entry = @trie.get(key)
|
92
|
+
if entry
|
93
|
+
entry.value
|
94
|
+
elsif default != Undefined
|
95
|
+
default
|
96
|
+
elsif block_given?
|
97
|
+
yield
|
98
|
+
else
|
99
|
+
raise KeyError.new("key not found: #{key.inspect}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
alias val_at fetch
|
103
|
+
|
104
|
+
def call(key, default=nil)
|
105
|
+
fetch(key, default)
|
106
|
+
end
|
107
|
+
|
108
|
+
def put(key, value = Undefined)
|
109
|
+
if value.equal?(Undefined)
|
110
|
+
put(key, yield(get(key)))
|
111
|
+
else
|
112
|
+
transform { @trie = @trie.put(key, value) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
alias assoc put
|
116
|
+
|
117
|
+
def delete(key)
|
118
|
+
trie = @trie.delete(key)
|
119
|
+
transform_unless(trie.equal?(@trie)) { @trie = trie }
|
120
|
+
end
|
121
|
+
alias without delete
|
122
|
+
alias dissoc delete
|
123
|
+
|
124
|
+
def each
|
125
|
+
return self unless block_given?
|
126
|
+
@trie.each { |entry| yield(entry.key, entry.value) }
|
127
|
+
end
|
128
|
+
|
129
|
+
def reduce(memo)
|
130
|
+
return memo unless block_given?
|
131
|
+
@trie.reduce(memo) { |memo, entry| yield(memo, entry.key, entry.value) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def merge(other)
|
135
|
+
transform { @trie = other.reduce(@trie, &:put) }
|
136
|
+
end
|
137
|
+
alias + merge
|
138
|
+
|
139
|
+
def keys
|
140
|
+
reduce(Hammock::EmptyList.new) { |keys, key, _| keys.cons(key) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def values
|
144
|
+
reduce(Hammock::EmptyList.new) { |values, _, value| values.cons(value) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def seq
|
148
|
+
return if count == 0
|
149
|
+
@trie.reduce(Hammock::EmptyList.new) { |entries, entry| entries.cons(entry)}
|
150
|
+
end
|
151
|
+
|
152
|
+
def cons(obj)
|
153
|
+
case obj
|
154
|
+
when Vector
|
155
|
+
assoc(obj.get(0), obj.get(1))
|
156
|
+
when Hamster::Trie::Entry
|
157
|
+
assoc(obj.key, obj.value)
|
158
|
+
when obj.respond_to?(:to_a)
|
159
|
+
put *obj
|
160
|
+
else
|
161
|
+
raise "You passed #{pair} as an argument to conj, but needs an Array-like arg"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def conj(pair)
|
166
|
+
return merge(pair) if Map === pair
|
167
|
+
if pair.respond_to?(:to_a)
|
168
|
+
put *pair
|
169
|
+
else
|
170
|
+
raise "You passed #{pair} as an argument to conj, but needs an Array-like arg"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def empty
|
175
|
+
self.class.new(meta)
|
176
|
+
end
|
177
|
+
|
178
|
+
def eql?(other)
|
179
|
+
instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie))
|
180
|
+
end
|
181
|
+
alias == eql?
|
182
|
+
|
183
|
+
def hash
|
184
|
+
reduce(0) { |hash, key, value| (hash << 32) - hash + key.hash + value.hash }
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_a
|
188
|
+
ret = []
|
189
|
+
each do |k,v|
|
190
|
+
ret << [k,v]
|
191
|
+
end
|
192
|
+
ret
|
193
|
+
end
|
194
|
+
alias pairs to_a
|
195
|
+
|
196
|
+
def inspect
|
197
|
+
out = []
|
198
|
+
each do |k, v|
|
199
|
+
out << "#{k.inspect} #{v.inspect}"
|
200
|
+
end
|
201
|
+
"{#{out.join(', ')}}"
|
202
|
+
end
|
203
|
+
alias to_s inspect
|
204
|
+
end
|
205
|
+
end
|