sequence 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +0 -0
- data/GPL +0 -0
- data/History.txt +27 -0
- data/Manifest.txt +2 -1
- data/README.txt +1 -1
- data/Rakefile +9 -2
- data/lib/assert.rb +0 -0
- data/lib/sequence.rb +9 -10
- data/lib/sequence/arraylike.rb +4 -0
- data/lib/sequence/buffered.rb +0 -40
- data/lib/sequence/circular.rb +0 -0
- data/lib/sequence/enum.rb +0 -0
- data/lib/sequence/file.rb +0 -0
- data/lib/sequence/functional.rb +0 -0
- data/lib/sequence/generator.rb +0 -0
- data/lib/sequence/indexed.rb +0 -0
- data/lib/sequence/io.rb +2 -2
- data/lib/sequence/list.rb +0 -0
- data/lib/sequence/ofhash.rb +0 -0
- data/lib/sequence/ofobjectivars.rb +0 -0
- data/lib/sequence/ofobjectmethods.rb +2 -2
- data/lib/sequence/position.rb +0 -0
- data/lib/sequence/reversed.rb +0 -0
- data/lib/sequence/shifting.rb +0 -0
- data/lib/sequence/singleitem.rb +0 -0
- data/lib/sequence/stringlike.rb +12 -3
- data/lib/sequence/subseq.rb +0 -0
- data/lib/sequence/usedata.rb +0 -0
- data/lib/sequence/version.rb +1 -1
- data/lib/sequence/weakrefset.rb +336 -0
- data/test/test.rb +0 -0
- data/test/test_all.rb +0 -0
- data/test/test_changes.rb +0 -0
- data/test/test_circulars.rb +0 -0
- data/test/test_rexscan.rb +24 -4
- data/test/test_seqrex.rb +0 -0
- data/test/test_sequences.rb +0 -0
- metadata +54 -44
- data/lib/weakrefset.rb +0 -254
data/COPYING
CHANGED
File without changes
|
data/GPL
CHANGED
File without changes
|
data/History.txt
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
=== 0.2.1 / 2009-01-07
|
2
|
+
* 1 Minor Enhancement:
|
3
|
+
* WeakRefSet now conforms to Set's api and tests more closely.
|
4
|
+
|
5
|
+
* 1 Bugfix:
|
6
|
+
* fixed errors in MatchData when matching against a Regexp with an
|
7
|
+
optional capture that didn't capture anything .
|
8
|
+
|
9
|
+
=== 0.2.0 / 2008-08-28
|
10
|
+
|
11
|
+
* 2 Major Enhancements:
|
12
|
+
* all tests now pass
|
13
|
+
* many small fixes in List; List is believed to actually work in all cases!
|
14
|
+
|
15
|
+
* 2 Minor Enhancements:
|
16
|
+
* cleanup of Indexed#scan*
|
17
|
+
* try not to use out of bounds indexes (including negative)
|
18
|
+
* more List test data created by wrapping existing data in List(s)
|
19
|
+
* enabled some tests which had been failing
|
20
|
+
|
21
|
+
=== 0.1.0 / 2006-10-05
|
22
|
+
|
23
|
+
* 1 major enhancement
|
24
|
+
|
25
|
+
* Birthday!
|
26
|
+
|
27
|
+
|
data/Manifest.txt
CHANGED
@@ -21,7 +21,7 @@ lib/sequence/stringlike.rb
|
|
21
21
|
lib/sequence/subseq.rb
|
22
22
|
lib/sequence/usedata.rb
|
23
23
|
lib/sequence/version.rb
|
24
|
-
lib/weakrefset.rb
|
24
|
+
lib/sequence/weakrefset.rb
|
25
25
|
test/test.rb
|
26
26
|
test/test_all.rb
|
27
27
|
test/test_changes.rb
|
@@ -34,3 +34,4 @@ Manifest.txt
|
|
34
34
|
README.txt
|
35
35
|
COPYING
|
36
36
|
GPL
|
37
|
+
History.txt
|
data/README.txt
CHANGED
data/Rakefile
CHANGED
@@ -5,13 +5,20 @@ require 'hoe'
|
|
5
5
|
require 'lib/sequence/version.rb'
|
6
6
|
|
7
7
|
|
8
|
-
|
8
|
+
if $*==["test"]
|
9
|
+
#hack to get 'rake test' to stay in one process
|
10
|
+
#which keeps netbeans happy
|
11
|
+
$:<<"lib"
|
12
|
+
require "test/test_all.rb"
|
13
|
+
Test::Unit::AutoRunner.run
|
14
|
+
exit
|
15
|
+
end
|
9
16
|
|
10
17
|
Hoe.new("sequence", Sequence::VERSION) do |_|
|
11
18
|
|
12
19
|
_.author = "Caleb Clausen"
|
13
20
|
_.email = "sequence-owner @at@ inforadical .dot. net"
|
14
|
-
_.url = "http://sequence.rubyforge.org/"
|
21
|
+
_.url = [ "http://sequence.rubyforge.org/", "http://rubyforge.org/projects/sequence"]
|
15
22
|
_.summary = "A single api for reading and writing sequential data types."
|
16
23
|
_.description = <<-END
|
17
24
|
A unified wrapper api for accessing data in Strings, Arrays, Files, IOs,
|
data/lib/assert.rb
CHANGED
File without changes
|
data/lib/sequence.rb
CHANGED
@@ -10,8 +10,9 @@
|
|
10
10
|
|
11
11
|
require "forwardable"
|
12
12
|
require 'assert'
|
13
|
-
require 'weakrefset'
|
14
13
|
require 'sequence/version'
|
14
|
+
require 'sequence/weakrefset'
|
15
|
+
|
15
16
|
|
16
17
|
=begin todo
|
17
18
|
|
@@ -30,6 +31,8 @@ match/matchback
|
|
30
31
|
|
31
32
|
class Sequence
|
32
33
|
|
34
|
+
# WeakRefSet=::Set
|
35
|
+
# warn "warning: sequence uses Set instead of WeakRefSet; memory leak results"
|
33
36
|
|
34
37
|
include Comparable
|
35
38
|
include Enumerable
|
@@ -81,13 +84,6 @@ class Sequence
|
|
81
84
|
end
|
82
85
|
|
83
86
|
|
84
|
-
# Return an empty object used for returning a sequence of elements.
|
85
|
-
# The only method required of this object is << (append to the sequence).
|
86
|
-
#usually [] or ""
|
87
|
-
def new_data
|
88
|
-
data_class.new
|
89
|
-
end
|
90
|
-
|
91
87
|
# attempt to read up to +len+ elements. the position is left just after the data read.
|
92
88
|
# #read may return less than the whole requested amount if less data than requested in
|
93
89
|
#+len+ is available. This can happen at end of file or if more data is simply unavailable
|
@@ -407,8 +403,8 @@ class Sequence
|
|
407
403
|
# numeric positions and also be tested
|
408
404
|
def position?(p)
|
409
405
|
case p
|
410
|
-
when Integer
|
411
|
-
when Position
|
406
|
+
when Integer; (-size..size)===p
|
407
|
+
when Position; equal? p.data
|
412
408
|
else equal? p
|
413
409
|
end
|
414
410
|
end
|
@@ -443,6 +439,7 @@ class Sequence
|
|
443
439
|
instance_variables.empty?
|
444
440
|
end
|
445
441
|
|
442
|
+
=begin who needs it?
|
446
443
|
# Compare +other+ (a Position or Integer) to the current position. return +1
|
447
444
|
# for the self is after, -1 for self being before, and 0 for it being at
|
448
445
|
# same location, nil (or false) if other is not a position of self.
|
@@ -451,6 +448,8 @@ class Sequence
|
|
451
448
|
elsif position?(other) then pos<=>other.pos
|
452
449
|
end
|
453
450
|
end
|
451
|
+
=end
|
452
|
+
|
454
453
|
#if passed an integer arg, return a new position decreased by len. if passed
|
455
454
|
# a position, return the distance (number
|
456
455
|
# or elements) from +other+ (a #position) to +self+. This can be +, -, or 0.
|
data/lib/sequence/arraylike.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
# Distributed under the terms of Ruby's license.
|
3
3
|
class Sequence
|
4
4
|
module ArrayLike
|
5
|
+
# Return an empty object used for returning a sequence of elements.
|
6
|
+
# The only method required of this object is << (append to the sequence).
|
7
|
+
#usually [] or ""
|
8
|
+
def new_data; [] end
|
5
9
|
def data_class; Array end
|
6
10
|
def like; ArrayLike end
|
7
11
|
|
data/lib/sequence/buffered.rb
CHANGED
@@ -43,46 +43,6 @@ class Buffered < Sequence
|
|
43
43
|
def new_data
|
44
44
|
@input.new_data
|
45
45
|
end
|
46
|
-
=begin
|
47
|
-
protected
|
48
|
-
def _delete1after?
|
49
|
-
v0 = @buffer.delete1after?
|
50
|
-
v0.nil? && @input && (v0 = @input.read(1)) && (v0 = v0[0])
|
51
|
-
v0
|
52
|
-
end
|
53
|
-
def _delete1before?
|
54
|
-
v0 = @buffer.delete1before?
|
55
|
-
v0.nil? && @output_pos>0 && (@output_pos -= 1;v0 = @output.read(-1)) && (v0 = v0[0])
|
56
|
-
v0
|
57
|
-
end
|
58
|
-
def _insert1before(v)
|
59
|
-
if not position?
|
60
|
-
len = @buffer.move!(true)
|
61
|
-
if @output
|
62
|
-
value = @buffer.read(len,nil)
|
63
|
-
value << v
|
64
|
-
@output.write(value)
|
65
|
-
else
|
66
|
-
@buffer.read(len,nil) if len
|
67
|
-
end
|
68
|
-
@output_pos += (len||0)+1
|
69
|
-
else
|
70
|
-
@buffer.insert1before(v)
|
71
|
-
end
|
72
|
-
true
|
73
|
-
end
|
74
|
-
def _insert1after(v)
|
75
|
-
@buffer.insert1after(v)
|
76
|
-
end
|
77
|
-
public
|
78
|
-
def close
|
79
|
-
if @output
|
80
|
-
@buffer.move!(true)
|
81
|
-
value = @buffer.read!(false) and @output.write(value)
|
82
|
-
end
|
83
|
-
super
|
84
|
-
end
|
85
|
-
=end
|
86
46
|
def _default_maxmatchlen; @buffer_size/2 end
|
87
47
|
|
88
48
|
attr :pos
|
data/lib/sequence/circular.rb
CHANGED
File without changes
|
data/lib/sequence/enum.rb
CHANGED
File without changes
|
data/lib/sequence/file.rb
CHANGED
File without changes
|
data/lib/sequence/functional.rb
CHANGED
File without changes
|
data/lib/sequence/generator.rb
CHANGED
File without changes
|
data/lib/sequence/indexed.rb
CHANGED
File without changes
|
data/lib/sequence/io.rb
CHANGED
@@ -36,7 +36,7 @@ class IO < Sequence
|
|
36
36
|
|
37
37
|
undef pos=, _pos=, position, position?, holding, holding?, holding!, readback, readback1
|
38
38
|
undef readahead, readbehind, readahead1, readbehind1, write#, write1
|
39
|
-
undef goto, move, move!, subseq, reversed,
|
39
|
+
undef goto, move, move!, subseq, reversed, +, -, succ, pred, begin, end
|
40
40
|
undef begin!, end!, first, last, slice, [], modify, []=
|
41
41
|
|
42
42
|
|
@@ -99,4 +99,4 @@ class IO < Sequence
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
|
-
end
|
102
|
+
end
|
data/lib/sequence/list.rb
CHANGED
File without changes
|
data/lib/sequence/ofhash.rb
CHANGED
File without changes
|
File without changes
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# Distributed under the terms of Ruby's license.
|
3
3
|
require 'sequence'
|
4
4
|
require 'sequence/indexed'
|
5
|
-
|
5
|
+
require 'sequence/functional'
|
6
6
|
require 'set'
|
7
7
|
|
8
8
|
|
@@ -84,4 +84,4 @@ class Sequence
|
|
84
84
|
return nil
|
85
85
|
end
|
86
86
|
end
|
87
|
-
end
|
87
|
+
end
|
data/lib/sequence/position.rb
CHANGED
File without changes
|
data/lib/sequence/reversed.rb
CHANGED
File without changes
|
data/lib/sequence/shifting.rb
CHANGED
File without changes
|
data/lib/sequence/singleitem.rb
CHANGED
File without changes
|
data/lib/sequence/stringlike.rb
CHANGED
@@ -4,6 +4,11 @@ require 'sequence/subseq'
|
|
4
4
|
|
5
5
|
class Sequence
|
6
6
|
module StringLike
|
7
|
+
# Return an empty object used for returning a sequence of elements.
|
8
|
+
# The only method required of this object is << (append to the sequence).
|
9
|
+
#usually [] or ""
|
10
|
+
def new_data; '' end
|
11
|
+
|
7
12
|
def data_class; String end
|
8
13
|
|
9
14
|
def like; StringLike end
|
@@ -218,9 +223,13 @@ class Sequence
|
|
218
223
|
begins=[]
|
219
224
|
ends=[]
|
220
225
|
matchdata.to_a.each_with_index{|substr,i|
|
221
|
-
|
222
|
-
|
223
|
-
|
226
|
+
if substr
|
227
|
+
begins<<matchdata.begin(i)+pos_adjust
|
228
|
+
ends<<matchdata.end(i)+pos_adjust
|
229
|
+
else
|
230
|
+
begins<<nil
|
231
|
+
ends<<nil
|
232
|
+
end
|
224
233
|
}
|
225
234
|
|
226
235
|
#..remove data at group indexes we added above
|
data/lib/sequence/subseq.rb
CHANGED
File without changes
|
data/lib/sequence/usedata.rb
CHANGED
File without changes
|
data/lib/sequence/version.rb
CHANGED
@@ -0,0 +1,336 @@
|
|
1
|
+
# $Id$
|
2
|
+
# Copyright (C) 2006,2008 Caleb Clausen
|
3
|
+
# Distributed under the terms of Ruby's license.
|
4
|
+
|
5
|
+
#require 'yaml'
|
6
|
+
require 'set'
|
7
|
+
begin
|
8
|
+
require 'weakref'
|
9
|
+
rescue Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
# WeakRefSet implements an unordered collection of weak references to objects.
|
13
|
+
# These references don't prevent garbage collection on these objects. As these
|
14
|
+
# objects are thrown away so does their entry in a WeakRefSet. Immmediate
|
15
|
+
# objects are not handled by this class (and wouldn't be useful).
|
16
|
+
class Sequence
|
17
|
+
class WeakRefSet<Set
|
18
|
+
include Enumerable
|
19
|
+
# create a new WeakRefSet from an optional Enumerable (of objects)
|
20
|
+
# which is optionally processed through a block
|
21
|
+
def initialize(items=nil,&block) # :yield: obj
|
22
|
+
items=[] if items.nil?
|
23
|
+
raise ArgumentError unless items.respond_to? :each
|
24
|
+
items=items.map(&block) if block
|
25
|
+
replace(items)
|
26
|
+
end
|
27
|
+
alias initialize_copy initialize
|
28
|
+
class<<self
|
29
|
+
def [] *items
|
30
|
+
new(items)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def finalizer(id)
|
36
|
+
@ids.delete(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
sss="a string"
|
40
|
+
if WeakRef.respond_to? :create_weakref and #rubinius
|
41
|
+
WeakRef.create_weakref(sss).at(0).equal?(sss)
|
42
|
+
|
43
|
+
def ref o
|
44
|
+
WeakRef.create_weakref o
|
45
|
+
end
|
46
|
+
def unref id
|
47
|
+
id.at(0)
|
48
|
+
rescue Exception
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
else
|
53
|
+
|
54
|
+
def ref o
|
55
|
+
o.__id__
|
56
|
+
end
|
57
|
+
def unref id
|
58
|
+
ObjectSpace._id2ref id
|
59
|
+
rescue RangeError
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
public
|
65
|
+
|
66
|
+
# add a weak reference to the set
|
67
|
+
def add(obj)
|
68
|
+
return self if include? obj
|
69
|
+
# Symbol===obj || Fixnum===obj || nil==obj || true==obj || false==obj and
|
70
|
+
# raise ArgumentError, "no immediates in weakrefset"
|
71
|
+
id=ref obj
|
72
|
+
case (o2=unref id) #test id for validity
|
73
|
+
when Fixnum;
|
74
|
+
obj.equal? o2 or raise
|
75
|
+
when Symbol,true,false,nil; id=obj #hopefully rare
|
76
|
+
else
|
77
|
+
obj.equal? o2 or raise
|
78
|
+
ObjectSpace.define_finalizer(obj,method(:finalizer))
|
79
|
+
end
|
80
|
+
@ids[id] = true
|
81
|
+
self
|
82
|
+
end
|
83
|
+
alias << add
|
84
|
+
|
85
|
+
# iterate over remaining valid objects in the set
|
86
|
+
def each
|
87
|
+
@ids.each_key { |id|
|
88
|
+
case id
|
89
|
+
when Integer
|
90
|
+
@ids.include?(id) or next
|
91
|
+
o = unref(id) or next
|
92
|
+
#i don't know where the random symbols come from, but at least they're always symbols...
|
93
|
+
else
|
94
|
+
o=id
|
95
|
+
end
|
96
|
+
# case o
|
97
|
+
# when Symbol,Fixnum,true,false,nil: warn "immediate value #{o.inspect} found in weakrefset"
|
98
|
+
# else
|
99
|
+
yield(o)
|
100
|
+
# end
|
101
|
+
}
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_a
|
106
|
+
map{|x| x}
|
107
|
+
end
|
108
|
+
|
109
|
+
def == other
|
110
|
+
return true if self.equal? other
|
111
|
+
other.is_a? Set and other.size==self.size and
|
112
|
+
all?{|x| other.include?(x) }
|
113
|
+
end
|
114
|
+
alias eql? ==
|
115
|
+
|
116
|
+
def hash
|
117
|
+
result=0
|
118
|
+
each{|x| result^=x.hash }
|
119
|
+
result
|
120
|
+
end
|
121
|
+
|
122
|
+
# clear the set (return self)
|
123
|
+
def clear
|
124
|
+
@ids = {}
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# merge some more objects into the set (return self)
|
129
|
+
def merge(enum)
|
130
|
+
enum.each { |obj| add(obj) }
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
# replace the objects in the set (return self)
|
135
|
+
def replace(enum)
|
136
|
+
clear
|
137
|
+
merge(enum)
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# delete an object in the set (return self)
|
142
|
+
def delete(obj)
|
143
|
+
delete?(obj)
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# delete an object in the set (return self if obj was found, else nil if nothing deleted)
|
148
|
+
def delete?(obj)
|
149
|
+
x=include?(obj)
|
150
|
+
if x
|
151
|
+
fail unless @ids.delete(ref( obj ))||@ids.delete(obj)
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Deletes every element of the set for which block evaluates to
|
157
|
+
# true, and returns self.
|
158
|
+
def delete_if
|
159
|
+
to_a.each { |o| delete(o) if yield(o) }
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
# is this object in the set?
|
164
|
+
def include?(obj)
|
165
|
+
any?{|x| obj==x}
|
166
|
+
end
|
167
|
+
alias member? include?
|
168
|
+
|
169
|
+
|
170
|
+
# return a human-readable string showing the set
|
171
|
+
def inspect
|
172
|
+
#unless $weakrefset_verbose_inspect
|
173
|
+
# return sprintf('#<%s:0x%x {...}>', self.class.name, object_id)
|
174
|
+
#end
|
175
|
+
ids = (Thread.current[:__weakrefset__inspect_key__] ||= [])
|
176
|
+
|
177
|
+
if ids.include?(object_id)
|
178
|
+
return sprintf('#<%s: {...}>', self.class.name)
|
179
|
+
end
|
180
|
+
|
181
|
+
begin
|
182
|
+
ids << object_id
|
183
|
+
return sprintf('#<%s: {%s}>', self.class.name, to_a.inspect[1..-2])
|
184
|
+
ensure
|
185
|
+
ids.pop
|
186
|
+
Thread.current[:__weakrefset__inspect_key__].empty? and
|
187
|
+
Thread.current[:__weakrefset__inspect_key__]=nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
if false #this is broken; emits yaml for a hash.
|
192
|
+
|
193
|
+
YAML::add_domain_type( "inforadical.net,2005", "object:WeakRefSet" ) do |type, val|
|
194
|
+
WeakRefSet.new( *val["items"] )
|
195
|
+
end
|
196
|
+
|
197
|
+
def is_complex_yaml?; true end
|
198
|
+
|
199
|
+
def to_yaml_type; "!inforadical.net,2005/object:WeakRefSet" end
|
200
|
+
|
201
|
+
alias to_yaml_properties to_a
|
202
|
+
|
203
|
+
def to_yaml( opts = {} )
|
204
|
+
YAML::quick_emit( object_id, opts ) { |out|
|
205
|
+
out.map( to_yaml_type ) { |map|
|
206
|
+
map.add( "items", to_yaml_properties)
|
207
|
+
}
|
208
|
+
}
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# remove some objects from the set (return self)
|
213
|
+
def subtract(enum)
|
214
|
+
enum.each { |obj| delete(obj) }
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns a new set containing elements exclusive between the set
|
219
|
+
# and the given enumerable object. (set ^ enum) is equivalent to
|
220
|
+
# ((set | enum) - (set & enum)).
|
221
|
+
def ^(enum)
|
222
|
+
enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable"
|
223
|
+
n = self.class.new(enum)
|
224
|
+
each { |o| if n.include?(o) then n.delete(o) else n.add(o) end }
|
225
|
+
n
|
226
|
+
end
|
227
|
+
|
228
|
+
# any objects in the set still valid?
|
229
|
+
def empty?
|
230
|
+
@ids.empty?
|
231
|
+
end
|
232
|
+
|
233
|
+
# number of objects in the set still valid
|
234
|
+
def size
|
235
|
+
@ids.size
|
236
|
+
end
|
237
|
+
alias length size
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# :stopdoc:
|
242
|
+
|
243
|
+
if __FILE__==$0
|
244
|
+
require 'benchmark'
|
245
|
+
class MyString <String;
|
246
|
+
def initialize(*)
|
247
|
+
@owner=0
|
248
|
+
super
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
class MyObject; end
|
253
|
+
class MyClass < Module; end
|
254
|
+
weakrefsets = (1..10).map {Sequence::WeakRefSet[]}
|
255
|
+
$stdout.sync=true
|
256
|
+
obj = nil
|
257
|
+
arr=[]
|
258
|
+
# srand(2389547343)
|
259
|
+
classes=[]
|
260
|
+
ObjectSpace.each_object(Class){|ob| classes<<ob}
|
261
|
+
maybe_Data=Data if defined? Data #whatever Data is supposed to be, idunno.....
|
262
|
+
classes-=[Symbol,Integer,NilClass,FalseClass,TrueClass,Numeric,maybe_Data,Bignum,Fixnum,
|
263
|
+
Float,Struct,Method,UnboundMethod,Proc,Thread,Binding,Continuation]
|
264
|
+
classes.delete_if{|k| begin k.allocate; rescue; true else false end}
|
265
|
+
def shuffle!(arr)
|
266
|
+
arr.sort_by{rand}
|
267
|
+
end
|
268
|
+
|
269
|
+
iterations=ARGV[0]||100_000
|
270
|
+
iterations=iterations.to_i
|
271
|
+
|
272
|
+
times = Benchmark.measure {
|
273
|
+
iterations.times { |i|
|
274
|
+
# print(weakrefs.size>70?"|":((60..70)===weakrefs.size ? ":" : (weakrefs.size>50?',':'.')))
|
275
|
+
print "." if 0==i%128
|
276
|
+
#obj = (k=classes[rand(classes.size)]).allocate
|
277
|
+
obj = (k=MyString).new "X#{rand(i+1)}_#{i}"
|
278
|
+
# obj= (k=Object).new
|
279
|
+
# obj= (k=MyObject).new
|
280
|
+
#obj= (k=MyClass).new
|
281
|
+
k==obj.class or raise
|
282
|
+
weakrefs=weakrefsets[rand(weakrefsets.size)]
|
283
|
+
obj.instance_eval{@owner=weakrefs}
|
284
|
+
obj.instance_eval{@owner}.equal? weakrefs or raise
|
285
|
+
weakrefs << obj
|
286
|
+
weakrefs.include?(obj) or raise
|
287
|
+
weakrefs.include?(obj) or raise
|
288
|
+
if rand(10).zero?
|
289
|
+
weakrefs.delete?(obj) or raise
|
290
|
+
!weakrefs.include?(obj) or raise
|
291
|
+
elsif rand(4).zero?
|
292
|
+
arr<<obj #prevent garbage collection
|
293
|
+
end
|
294
|
+
if rand(1000).zero?
|
295
|
+
shuffle! arr
|
296
|
+
arr.slice!(0..rand(arr.size))
|
297
|
+
end
|
298
|
+
arr.each{|o| o.instance_eval{@owner}.include? o or raise }
|
299
|
+
#rand(100).zero? and GC.start
|
300
|
+
}
|
301
|
+
}
|
302
|
+
puts
|
303
|
+
GC.start
|
304
|
+
weakrefsets.each{|weakrefs|
|
305
|
+
weakrefs.clear
|
306
|
+
weakrefs.size.zero? or raise
|
307
|
+
weakrefs.empty? or raise
|
308
|
+
}
|
309
|
+
puts(times)
|
310
|
+
|
311
|
+
|
312
|
+
|
313
|
+
|
314
|
+
class NotReallyWeakRefSet<Sequence::WeakRefSet
|
315
|
+
#ensure the items referenced never get gc'd
|
316
|
+
class<<self
|
317
|
+
@@keeprefs=[]
|
318
|
+
def new(*args)
|
319
|
+
@@keeprefs.concat args
|
320
|
+
super
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
#"now I should reuse Set's original tests, but replacing Set with NotReallyWeakRefSet"
|
326
|
+
origset=$:.find{|dir| File.exist? dir+"/set.rb"} +"/set.rb"
|
327
|
+
testcode=File.read(origset).split("\n__END__\n",2).last
|
328
|
+
testcode=testcode.split("\nclass TC_SortedSet",2).first #hack off some unwanted tests
|
329
|
+
testcode.gsub! 'Set', 'NotReallyWeakRefSet'
|
330
|
+
|
331
|
+
eval testcode
|
332
|
+
end
|
333
|
+
|
334
|
+
# :stopdoc:
|
335
|
+
|
336
|
+
|
data/test/test.rb
CHANGED
File without changes
|
data/test/test_all.rb
CHANGED
File without changes
|
data/test/test_changes.rb
CHANGED
File without changes
|
data/test/test_circulars.rb
CHANGED
File without changes
|
data/test/test_rexscan.rb
CHANGED
@@ -57,6 +57,7 @@ $Debug=true
|
|
57
57
|
test_read
|
58
58
|
test_slice
|
59
59
|
test_slice_empty
|
60
|
+
test_optional_capture
|
60
61
|
]
|
61
62
|
MODIFYMETHODS=%w[
|
62
63
|
test_insert
|
@@ -257,8 +258,8 @@ $Debug=true
|
|
257
258
|
const_set k, xk=Class.new(xk)
|
258
259
|
xk.instance_eval do
|
259
260
|
define_method :a_seq do
|
260
|
-
result=super
|
261
|
-
result.maxmatchlen=
|
261
|
+
result=super()
|
262
|
+
result.maxmatchlen=10
|
262
263
|
result
|
263
264
|
end
|
264
265
|
end
|
@@ -273,7 +274,7 @@ $Debug=true
|
|
273
274
|
const_set k, xk=Class.new(xk)
|
274
275
|
xk.instance_eval do
|
275
276
|
define_method :a_seq do
|
276
|
-
result=Sequence::List.new([wrappee=super])
|
277
|
+
result=Sequence::List.new([wrappee=super()])
|
277
278
|
result.pos=wrappee.pos
|
278
279
|
return result
|
279
280
|
end
|
@@ -290,7 +291,7 @@ $Debug=true
|
|
290
291
|
const_set k, xk=Class.new(xk)
|
291
292
|
xk.instance_eval do
|
292
293
|
define_method :a_seq do
|
293
|
-
result=Sequence::List.new([wrappee=super])
|
294
|
+
result=Sequence::List.new([wrappee=super()])
|
294
295
|
chunk=rand_pos_pair
|
295
296
|
chunk=chunk.first..chunk.last
|
296
297
|
result[chunk]=result[chunk]
|
@@ -344,6 +345,25 @@ $Debug=true
|
|
344
345
|
=end
|
345
346
|
|
346
347
|
class Indexed
|
348
|
+
def test_optional_capture
|
349
|
+
seq=a_seq
|
350
|
+
word=seq.scan /(more of )?that (tough)/
|
351
|
+
md=seq.last_match
|
352
|
+
assert_equal "that tough", word
|
353
|
+
assert_equal "that tough", md[0]
|
354
|
+
assert_equal nil, md[1]
|
355
|
+
assert_equal "tough", md[2]
|
356
|
+
|
357
|
+
assert_equal OFFSET, md.begin(0)
|
358
|
+
assert_equal OFFSET+10, md.end(0)
|
359
|
+
assert_equal nil, md.begin(1)
|
360
|
+
assert_equal nil, md.end(1)
|
361
|
+
assert_equal OFFSET+5, md.begin(2)
|
362
|
+
assert_equal OFFSET+10, md.end(2)
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
|
347
367
|
RANDOMIZED_METHODS=[:test_slice,:test_insert,:test_delete,:test_modify]
|
348
368
|
undef test_randomized_methods_some_more
|
349
369
|
def test_randomized_methods_some_more n=50
|
data/test/test_seqrex.rb
CHANGED
File without changes
|
data/test/test_sequences.rb
CHANGED
File without changes
|
metadata
CHANGED
@@ -1,33 +1,37 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: sequence
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2008-08-29 00:00:00 +01:00
|
8
|
-
summary: A single api for reading and writing sequential data types.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: sequence-owner @at@ inforadical .dot. net
|
12
|
-
homepage: http://sequence.rubyforge.org/
|
13
|
-
rubyforge_project: sequence
|
14
|
-
description: A unified wrapper api for accessing data in Strings, Arrays, Files, IOs, and Enumerations. Each sequence encapsulates some data and a current position within it. There are methods for moving the position, reading and writing data (with or without moving the position) forward or backward from the current position (or anywhere at all), scanning for patterns (like StringScanner, but it works in Files too, among others), and saving a position that will remain valid even after data is deleted or inserted elsewhere within the sequence. There are also some utility classes for making sequences reversed or circular, turning one-way sequences into two-way, buffering, and making sequences that are subsets or aggregations of existing sequences.
|
15
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 0.2.1
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Caleb Clausen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-06 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.12.2
|
24
|
+
version:
|
25
|
+
description: A unified wrapper api for accessing data in Strings, Arrays, Files, IOs, and Enumerations. Each sequence encapsulates some data and a current position within it. There are methods for moving the position, reading and writing data (with or without moving the position) forward or backward from the current position (or anywhere at all), scanning for patterns (like StringScanner, but it works in Files too, among others), and saving a position that will remain valid even after data is deleted or inserted elsewhere within the sequence. There are also some utility classes for making sequences reversed or circular, turning one-way sequences into two-way, buffering, and making sequences that are subsets or aggregations of existing sequences.
|
26
|
+
email: sequence-owner @at@ inforadical .dot. net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- Manifest.txt
|
33
|
+
- README.txt
|
34
|
+
- History.txt
|
31
35
|
files:
|
32
36
|
- lib/assert.rb
|
33
37
|
- lib/sequence.rb
|
@@ -52,7 +56,7 @@ files:
|
|
52
56
|
- lib/sequence/subseq.rb
|
53
57
|
- lib/sequence/usedata.rb
|
54
58
|
- lib/sequence/version.rb
|
55
|
-
- lib/weakrefset.rb
|
59
|
+
- lib/sequence/weakrefset.rb
|
56
60
|
- test/test.rb
|
57
61
|
- test/test_all.rb
|
58
62
|
- test/test_changes.rb
|
@@ -65,27 +69,33 @@ files:
|
|
65
69
|
- README.txt
|
66
70
|
- COPYING
|
67
71
|
- GPL
|
68
|
-
|
69
|
-
|
72
|
+
- History.txt
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://sequence.rubyforge.org/
|
75
|
+
post_install_message:
|
70
76
|
rdoc_options:
|
71
77
|
- --main
|
72
78
|
- README.txt
|
73
|
-
|
74
|
-
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: "0"
|
92
|
+
version:
|
80
93
|
requirements: []
|
81
94
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: 1.5.1
|
91
|
-
version:
|
95
|
+
rubyforge_project: sequence
|
96
|
+
rubygems_version: 1.3.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 2
|
99
|
+
summary: A single api for reading and writing sequential data types.
|
100
|
+
test_files:
|
101
|
+
- test/test_all.rb
|
data/lib/weakrefset.rb
DELETED
@@ -1,254 +0,0 @@
|
|
1
|
-
# $Id$
|
2
|
-
# Copyright (C) 2006 Caleb Clausen
|
3
|
-
# Distributed under the terms of Ruby's license.
|
4
|
-
|
5
|
-
require 'yaml'
|
6
|
-
require 'assert'
|
7
|
-
|
8
|
-
# WeakRefSet implements an unordered collection of weak references to objects.
|
9
|
-
# These references don't prevent garbage collection on these objects. As these
|
10
|
-
# objects are thrown away so does their entry in a WeakRefSet. Immmediate
|
11
|
-
# objects are not handled by this class (and wouldn't be useful).
|
12
|
-
class WeakRefSet
|
13
|
-
include Enumerable
|
14
|
-
# create a new WeakRefSet from an optional Enumerable (of objects)
|
15
|
-
# which is optionally processed through a block
|
16
|
-
def initialize(items) # :yield: obj
|
17
|
-
replace(items)
|
18
|
-
end
|
19
|
-
class<<self
|
20
|
-
def [] *items
|
21
|
-
new(items)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
def finalizer(id)
|
27
|
-
@ids.delete(id)
|
28
|
-
end
|
29
|
-
|
30
|
-
public
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# add a weak reference to the set
|
35
|
-
def add(obj)
|
36
|
-
Symbol===obj || Fixnum===obj || nil==obj || true==obj || false==obj and
|
37
|
-
raise ArgumentError, "no immediates in weakrefset"
|
38
|
-
id=obj.object_id
|
39
|
-
case (o2=ObjectSpace._id2ref id) #test id for validity
|
40
|
-
when Symbol,Fixnum,true,false,nil: id=obj #hopefully rare
|
41
|
-
else obj.equal? o2 or raise
|
42
|
-
ObjectSpace.define_finalizer(obj,method(:finalizer))
|
43
|
-
end
|
44
|
-
@ids[id] = true
|
45
|
-
self
|
46
|
-
end
|
47
|
-
alias << add
|
48
|
-
# iterate over remaining valid objects in the set
|
49
|
-
def each
|
50
|
-
@ids.each_key { |id|
|
51
|
-
case id
|
52
|
-
when Integer:
|
53
|
-
begin
|
54
|
-
o = ObjectSpace._id2ref(id)
|
55
|
-
rescue RangeError
|
56
|
-
next
|
57
|
-
end
|
58
|
-
@ids.include?(id) or next
|
59
|
-
#i don't know where the random symbols come from, but at least they're always symbols...
|
60
|
-
else
|
61
|
-
o=id
|
62
|
-
end
|
63
|
-
case o
|
64
|
-
when Symbol,Fixnum,true,false,nil: warn "immediate value #{o.inspect} found in weakrefset"
|
65
|
-
else yield(o)
|
66
|
-
end
|
67
|
-
}
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
def == other
|
72
|
-
size==other.size and
|
73
|
-
each{|x|
|
74
|
-
other.include? x or return
|
75
|
-
}
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
# clear the set (return self)
|
80
|
-
def clear
|
81
|
-
@ids = {}
|
82
|
-
self
|
83
|
-
end
|
84
|
-
# merge some more objects into the set (return self)
|
85
|
-
def merge(enum)
|
86
|
-
enum.each { |obj| add(obj) }
|
87
|
-
self
|
88
|
-
end
|
89
|
-
# replace the objects in the set (return self)
|
90
|
-
def replace(enum)
|
91
|
-
clear
|
92
|
-
merge(enum)
|
93
|
-
self
|
94
|
-
end
|
95
|
-
# delete an object in the set (return self)
|
96
|
-
def delete(obj)
|
97
|
-
delete?(obj)
|
98
|
-
self
|
99
|
-
end
|
100
|
-
# delete an object in the set (return self or nil if nothing deleted)
|
101
|
-
def delete?(obj)
|
102
|
-
x=include?(obj) and @ids.delete(x.__id__)||@ids.delete(x) and self
|
103
|
-
end
|
104
|
-
# is this object in the set?
|
105
|
-
def include?(obj)
|
106
|
-
find{|x| obj==x}
|
107
|
-
end
|
108
|
-
alias member? include?
|
109
|
-
|
110
|
-
# return a human-readable string showing the set
|
111
|
-
def inspect
|
112
|
-
#unless $weakrefset_verbose_inspect
|
113
|
-
# return sprintf('#<%s:0x%x {...}>', self.class.name, object_id)
|
114
|
-
#end
|
115
|
-
ids = (Thread.current[:__weakrefset__inspect_key__] ||= [])
|
116
|
-
|
117
|
-
if ids.include?(object_id)
|
118
|
-
return sprintf('#<%s {...}>', self.class.name)
|
119
|
-
end
|
120
|
-
|
121
|
-
begin
|
122
|
-
ids << object_id
|
123
|
-
return sprintf('#<%s {%s}>', self.class.name, to_a.inspect[1..-2])
|
124
|
-
ensure
|
125
|
-
ids.pop
|
126
|
-
Thread.current[:__weakrefset__inspect_key__].empty? and
|
127
|
-
Thread.current[:__weakrefset__inspect_key__]=nil
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
if false #this is broken; emits yaml for a hash.
|
132
|
-
|
133
|
-
YAML::add_domain_type( "inforadical.net,2005", "object:WeakRefSet" ) do |type, val|
|
134
|
-
WeakRefSet.new( *val["items"] )
|
135
|
-
end
|
136
|
-
|
137
|
-
def is_complex_yaml?; true end
|
138
|
-
|
139
|
-
def to_yaml_type; "!inforadical.net,2005/object:WeakRefSet" end
|
140
|
-
|
141
|
-
alias to_yaml_properties to_a
|
142
|
-
|
143
|
-
def to_yaml( opts = {} )
|
144
|
-
YAML::quick_emit( object_id, opts ) { |out|
|
145
|
-
out.map( to_yaml_type ) { |map|
|
146
|
-
map.add( "items", to_yaml_properties)
|
147
|
-
}
|
148
|
-
}
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# remove some objects from the set (return self)
|
153
|
-
def subtract(enum)
|
154
|
-
enum.each { |obj| delete(obj) }
|
155
|
-
self
|
156
|
-
end
|
157
|
-
# any objects in the set still valid?
|
158
|
-
def empty?
|
159
|
-
@ids.empty?
|
160
|
-
end
|
161
|
-
# number of objects in the set still valid
|
162
|
-
def size
|
163
|
-
@ids.size
|
164
|
-
end
|
165
|
-
alias length size
|
166
|
-
end
|
167
|
-
|
168
|
-
# :stopdoc:
|
169
|
-
|
170
|
-
if __FILE__==$0
|
171
|
-
require 'benchmark'
|
172
|
-
class MyString <String;
|
173
|
-
def initialize(*)
|
174
|
-
@owner=0
|
175
|
-
super
|
176
|
-
end
|
177
|
-
|
178
|
-
end
|
179
|
-
class MyObject; end
|
180
|
-
class MyClass < Module; end
|
181
|
-
weakrefsets = (1..10).map {WeakRefSet[]}
|
182
|
-
$stdout.sync=true
|
183
|
-
obj = nil
|
184
|
-
arr=[]
|
185
|
-
# srand(2389547343)
|
186
|
-
classes=[]
|
187
|
-
ObjectSpace.each_object(Class){|ob| classes<<ob}
|
188
|
-
classes-=[Symbol,Integer,NilClass,FalseClass,TrueClass,Numeric,Data,Bignum,Fixnum,
|
189
|
-
Float,Struct,Method,UnboundMethod,Proc,Thread,Binding,Continuation]
|
190
|
-
classes.delete_if{|k| begin k.allocate; rescue; true else false end}
|
191
|
-
def shuffle!(arr)
|
192
|
-
arr.sort_by{rand}
|
193
|
-
end
|
194
|
-
times = Benchmark.measure {
|
195
|
-
100000.times { |i|
|
196
|
-
# print(weakrefs.size>70?"|":((60..70)===weakrefs.size ? ":" : (weakrefs.size>50?',':'.')))
|
197
|
-
print "." if 0==i%128
|
198
|
-
#obj = (k=classes[rand(classes.size)]).allocate
|
199
|
-
obj = (k=MyString).new "X" #*rand(i+1)
|
200
|
-
# obj= (k=Object).new
|
201
|
-
# obj= (k=MyObject).new
|
202
|
-
#obj= (k=MyClass).new
|
203
|
-
k==obj.class or raise
|
204
|
-
weakrefs=weakrefsets[rand(weakrefsets.size)]
|
205
|
-
obj.instance_eval{@owner=weakrefs}
|
206
|
-
obj.instance_eval{@owner}.equal? weakrefs or raise
|
207
|
-
weakrefs.each { |o|
|
208
|
-
# k==o.class or raise "set contained a #{o.class}. i=#{i}. size=#{weakrefs.size}"
|
209
|
-
(o2=o.instance_eval{@owner})==weakrefs or
|
210
|
-
raise "expected owner #{weakrefs.map{|w| w.__id__}.inspect}, "+
|
211
|
-
"got #{o2.inspect}, item #{o}, id #{o.__id__}, obj #{obj.__id__}"
|
212
|
-
}
|
213
|
-
weakrefs << obj
|
214
|
-
weakrefs.each { |o|
|
215
|
-
# k==o.class or raise "set contained a #{o.class}. i=#{i}. size=#{weakrefs.size}"
|
216
|
-
(o2=o.instance_eval{@owner})==weakrefs or
|
217
|
-
raise "expected owner #{weakrefs.map{|w| w.__id__}.inspect}, "+
|
218
|
-
"got #{o2.inspect}, item #{o}, id #{o.__id__}, obj #{obj.__id__}"
|
219
|
-
}
|
220
|
-
weakrefs.include?(obj) or raise
|
221
|
-
weakrefs.each { |o|
|
222
|
-
# k==o.class or raise "set contained a #{o.class}. i=#{i}. size=#{weakrefs.size}"
|
223
|
-
(o2=o.instance_eval{@owner})==weakrefs or
|
224
|
-
raise "expected owner #{weakrefs.map{|w| w.__id__}.inspect}, "+
|
225
|
-
"got #{o2.inspect}, item #{o}, id #{o.__id__}, obj #{obj.__id__}"
|
226
|
-
}
|
227
|
-
weakrefs.include?(obj) or raise
|
228
|
-
if rand(10).zero?
|
229
|
-
weakrefs.delete?(obj) or raise
|
230
|
-
!weakrefs.include?(obj) or raise
|
231
|
-
elsif rand(4).zero?
|
232
|
-
arr<<obj #prevent garbage collection
|
233
|
-
end
|
234
|
-
if rand(1000).zero?
|
235
|
-
shuffle! arr
|
236
|
-
arr.slice!(0..rand(arr.size))
|
237
|
-
end
|
238
|
-
arr.each{|o| o.instance_eval{@owner}.include? o or raise }
|
239
|
-
#rand(100).zero? and GC.start
|
240
|
-
}
|
241
|
-
}
|
242
|
-
puts
|
243
|
-
GC.start
|
244
|
-
weakrefsets.each{|weakrefs|
|
245
|
-
weakrefs.clear
|
246
|
-
weakrefs.size.zero? or raise
|
247
|
-
weakrefs.empty? or raise
|
248
|
-
}
|
249
|
-
puts(times)
|
250
|
-
end
|
251
|
-
|
252
|
-
# :stopdoc:
|
253
|
-
|
254
|
-
|