/
array.rb
1953 lines (1627 loc) · 54.4 KB
/
array.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# depends on: class.rb enumerable.rb tuple.rb kernel.rb
##
# Arrays are ordered, integer-indexed collections of any object. Array
# indexing starts at 0, as in C or Java. A negative index is assumed to be
# relative to the end of the array---that is, an index of -1 indicates the
# last element of the array, -2 is the next to last element in the array, and
# so on.
#
# Arrays can be created with the <tt>[]</tt> syntax, or via <tt>Array.new</tt>.
class Array
ivar_as_index :total => 0, :tuple => 1, :start => 2, :shared => 3
def total ; @total ; end
def tuple ; @tuple ; end
def start ; @start ; end
def __ivars__; nil ; end
include Enumerable
# The flow control for many of these methods is
# pretty evil due to how MRI works. There is
# also a lot of duplication of code due to very
# subtle processing differences and, in some
# cases, to avoid mutual dependency. Apologies.
# Returns a new Array populated with the given objects
def self.[](*args)
new args
end
def self.new(*args, &block)
raise ArgumentError, "Wrong number of arguments, #{args.size} for 2" if args.size > 2
ary = allocate
ary.__send__ :setup
ary.__send__ :initialize, *args, &block
ary
end
# At present, @tuple.kind_of?(Tuple) is essentially an invariant for Array.
# If we relax this and ensure that every method behaves correctly if
# @tuple == nil, then we can omit initializing @tuple here. We cannot
# easily know whether #initialize is defined in a subclass when this
# method is called.
def setup
@start = 0
@total = 0
@tuple = Tuple.new 2
end
# Creates a new Array. Without arguments, an empty
# Array is returned. If the only argument is an object
# that responds to +to_ary+, a copy of that Array is
# created. Otherwise the first argument is the size
# of the new Array (default 0.) The second argument
# may be an object used to fill the Array up to the
# size given (the same object, not a copy.) Alternatively,
# a block that takes the numeric index can be given and
# will be run size times to fill the Array with its
# result. The block supercedes any object given. If
# neither is provided, the Array is filled with nil.
def initialize(*args)
unless args.empty?
if args.size == 1 and (args.first.__kind_of__ Array or args.first.respond_to? :to_ary)
ary = Type.coerce_to args.first, Array, :to_ary
tuple = Tuple.new(ary.size + 10)
@total = ary.size
tuple.copy_from ary.tuple, ary.start, 0
@tuple = tuple
else
count = Type.coerce_to args.first, Fixnum, :to_int
raise ArgumentError, "Size must be positive" if count < 0
obj = args[1]
@tuple = Tuple.new(count + 10)
@total = count
if block_given?
count.times { |i| @tuple.put i, yield(i) }
else
count.times { |i| @tuple.put i, obj }
end
end
end
self
end
private :initialize
# Element reference, returns the element at the given index or
# a subarray starting from the index continuing for length
# elements or returns a subarray from range elements. Negative
# indices count from the end. Returns nil if the index or subarray
# request cannot be completed. Array#slice is synonymous with #[].
# Subclasses return instances of themselves.
def [](one, two = nil)
Ruby.primitive :array_aref
# Normalise the argument variants
start, finish, count, simple, is_range = nil, nil, nil, false, false
if one.kind_of? Range
is_range = true
start, finish = one.begin, one.end
elsif two
start, count = one, Type.coerce_to(two, Fixnum, :to_int)
return nil if count < 0 # No need to go further
else
start, finish, simple = one, one, true
end
# Convert negative indices
start = Type.coerce_to start, Fixnum, :to_int
start += @total if start < 0
if simple
return nil if start < 0 or start >= @total
return @tuple.at(@start + start)
# ONE past end only, MRI compat
elsif start == @total
return self.class.new
elsif start < 0 or start >= @total
return nil
end
finish = Type.coerce_to finish, Fixnum, :to_int if finish
finish = (start + count - 1) if count # For non-ranges
finish += @total if finish < 0
finish -= 1 if is_range and one.exclude_end?
# Going past the end is ignored (sort of)
finish = (@total - 1) if finish >= @total
if finish < 0
return self.class.new if is_range
return nil
end
return self.class.new if finish < start
return self.class.new if count == 0
out = self.class.new
start.upto(finish) { |i| out << at(i) }
out
end
alias_method :slice, :[]
def []=(idx, ent, *args)
Ruby.primitive :array_aset
cnt = nil
if args.size != 0
cnt = ent.to_int
ent = args[0] # 2nd arg (cnt) is the optional one!
end
# Normalise Ranges
if idx.is_a?(Range)
if cnt
raise ArgumentError, "Second argument invalid with a range"
end
unless idx.first.respond_to?(:to_int)
raise TypeError, "can't convert #{idx.first.class} into Integer"
end
unless idx.last.respond_to?(:to_int)
raise TypeError, "can't convert #{idx.last.class} into Integer"
end
lst = idx.last.to_int
if lst < 0
lst += @total
end
lst += 1 unless idx.exclude_end?
idx = idx.first.to_int
if idx < 0
idx += @total
raise RangeError if idx < 0
end
# m..n, m > n allowed
lst = idx if idx > lst
cnt = lst - idx
end
idx = idx.to_int
if idx < 0
idx += @total
raise IndexError.new("Index #{idx -= @total} out of bounds") if idx < 0
end
if cnt
# count < 0 not allowed
raise IndexError.new("Negative length #{cnt}") if cnt < 0
cnt = @total - idx if cnt > @total - idx # MRI seems to be forgiving here!
if ent.nil?
replacement = []
elsif ent.is_a?(Array)
replacement = ent
elsif ent.respond_to?(:to_ary)
replacement = ent.to_ary
else
replacement = [ent]
end
if replacement.size > cnt
newtotal = @total + replacement.size - cnt
if newtotal > @tuple.fields - @start
nt = Tuple.new(newtotal + 10)
nt.copy_from @tuple, @start, 0 # FIXME: double copy of right part
@start = 0
@tuple = nt
end # this should be an else
f = @total
t = newtotal
while f > idx + cnt
t -= 1
f -= 1
@tuple.put(@start+t, @tuple.at(@start+f))
end
@total = newtotal
end
replacement.each_with_index { |el, i|
@tuple.put(@start+idx+i, el)
}
if replacement.size < cnt
f = @start + idx + cnt
t = @start + idx + replacement.size
# shift fields to the left
while f < @total
@tuple.put(t, @tuple.at(f))
t += 1
f += 1
end
# unset any extraneous fields
while t < @tuple.fields
@tuple.put(t, nil)
t += 1
end
@total -= (cnt - replacement.size)
end
return ent
end
nt = @start + idx + 1
reallocate(nt) if @tuple.size < nt
@tuple.put @start + idx, ent
if idx >= @total - 1
@total = idx + 1
end
return ent
end
# Appends the object to the end of the Array.
# Returns self so several appends can be chained.
def <<(obj)
nt = @start + @total + 1
reallocate nt if @tuple.size < nt
@tuple.put @start + @total, obj
@total += 1
self
end
# Creates a new Array containing only elements common to
# both Arrays, without duplicates. Also known as a 'set
# intersection'
def &(other)
other = Type.coerce_to other, Array, :to_ary
out, set_include = [], {}
other.each { |x| set_include[x] = [true, x] }
each { |x|
if set_include[x] and set_include[x].last.eql?(x)
out << x
set_include[x] = false
end
}
out
end
# Creates a new Array by combining the two Arrays' items,
# without duplicates. Also known as a 'set union.'
def |(other)
other = Type.coerce_to other, Array, :to_ary
out, exclude = [], {}
(self + other).each { |x|
unless exclude[x]
out << x
exclude[x] = true
end
}
out
end
# Repetition operator when supplied a #to_int argument:
# returns a new Array as a concatenation of the given number
# of the original Arrays. With an argument that responds to
# #to_str, functions exactly like #join instead.
def *(val)
if val.respond_to? :to_str
return join(val)
else
# Aaargh stupid MRI's stupid specific stupid error stupid types stupid
val = Type.coerce_to val, Fixnum, :to_int
raise ArgumentError, "Count cannot be negative" if val < 0
out = self.class.new
val.times { out.push(*self) }
out
end
end
# Create a concatenation of the two Arrays.
def +(other)
other = Type.coerce_to other, Array, :to_ary
out = []
each { |e| out << e }
other.each { |e| out << e }
out
end
# Creates a new Array that contains the items of the original
# Array that do not appear in the other Array, effectively
# 'deducting' those items. The matching method is Hash-based.
def -(other)
other = Type.coerce_to other, Array, :to_ary
out, exclude = [], {}
other.each { |x| exclude[x] = true }
each { |x| out << x unless exclude[x] }
out
end
# Compares the two Arrays and returns -1, 0 or 1 depending
# on whether the first one is 'smaller', 'equal' or 'greater'
# in relation to the second. Two Arrays are equal only if all
# their elements are 0 using first_e <=> second_e and their
# lengths are the same. The element comparison is the primary
# and length is only checked if the former results in 0's.
def <=>(other)
other = Type.coerce_to other, Array, :to_ary
size.times { |i|
return 1 unless other.size > i
diff = at(i) <=> other.at(i)
return diff if diff != 0
}
return 1 if size > other.size
return -1 if size < other.size
0
end
# The two Arrays are considered equal only if their
# lengths are the same and each of their elements
# are equal according to first_e == second_e . Both
# Array subclasses and to_ary objects are accepted.
def ==(other)
unless other.kind_of? Array
return false unless other.respond_to? :to_ary
other = other.to_ary
end
return false unless size == other.size
size.times { |i| return false unless @tuple.at(@start + i) == other.at(i) }
true
end
# Assumes the Array contains other Arrays and searches through
# it comparing the given object with the first element of each
# contained Array using elem == obj. Returns the first contained
# Array that matches (the first 'associated' Array) or nil.
def assoc(obj)
# FIX: use break when it works again
found, res = nil, nil
each { |elem|
if found.nil? and elem.kind_of? Array and elem.first == obj
found, res = true, elem
end
}
res
end
# Returns the element at the given index. If the
# index is negative, counts from the end of the
# Array. If the index is out of range, nil is
# returned. Slightly faster than +Array#[]+
def at(idx)
Ruby.primitive :array_aref
idx = Type.coerce_to idx, Fixnum, :to_int
idx += @total if idx < 0
return nil if idx < 0 or idx >= @total
@tuple.at @start + idx
end
# Removes all elements in the Array and leaves it empty
def clear()
@tuple = Tuple.new(1)
@total = 0
@start = 0
self
end
# Returns a copy of self with all nil elements removed
def compact()
dup.compact! || self
end
# Removes all nil elements from self, returns nil if no changes
# TODO: Needs improvement
def compact!()
i = @start
tot = @start + @total
# Low-level because pretty much anything else breaks everything
while i < tot
if @tuple.at(i).nil?
j = i
i += 1
while i < tot
if @tuple.at(i) != nil
@tuple.put j, @tuple.at(i)
j += 1
end
i += 1
end
# OK to leave tuple size larger?
@total = j - @start
return self
end
i += 1
end
nil
end
# Appends the elements in the other Array to self
def concat(other)
ary = Type.coerce_to(other, Array, :to_ary)
size = @total + ary.size
tuple = Tuple.new size
tuple.copy_from @tuple, @start, 0 if @total > 0
tuple.copy_from ary.tuple, ary.start, @total
@tuple = tuple
@start = 0
@total = size
self
end
# Stupid subtle differences prevent proper reuse in these three
# Removes all elements from self that are #== to the given object.
# If the object does not appear at all, nil is returned unless a
# block is provided in which case the value of running it is
# returned instead.
def delete(obj)
i = @start
tot = @start + @total
# Leaves the tuple to the original size still
while i < tot
if @tuple.at(i) == obj
j = i
i += 1
while i < tot
if @tuple.at(i) != obj
@tuple.put(j, @tuple.at(i))
j += 1
end
i += 1
end
@total = j - @start
return obj
end
i += 1
end
yield if block_given?
end
# Deletes the element at the given index and returns
# the deleted element or nil if the index is out of
# range. Negative indices count backwards from end.
def delete_at(idx)
idx = Type.coerce_to idx, Fixnum, :to_int
# Flip to positive and weed out out of bounds
idx += @total if idx < 0
return nil if idx < 0 or idx >= @total
# Grab the object and adjust the indices for the rest
obj = @tuple.at(@start + idx)
idx.upto(@total - 2) { |i| @tuple.put(@start + i, @tuple.at(@start + i + 1)) }
@tuple.put(@start + @total - 1, nil)
@total -= 1
obj
end
# Deletes every element from self for which block evaluates to true
def delete_if()
i = @start
tot = @total + @start
# Leaves the tuple to the original size still
while i < tot
if yield @tuple.at(i)
j = i
i += 1
while i < tot
unless yield @tuple.at(i)
@tuple.put(j, @tuple.at(i))
j += 1
end
i += 1
end
@total = j - @start
return self
end
i += 1
end
return self
end
# Passes each element in the Array to the given block
# and returns self. We re-evaluate @total each time
# through the loop in case the array has changed.
def each()
i = 0
while i < @total
yield at(i)
i += 1
end
self
end
# Passes each index of the Array to the given block
# and returns self. We re-evaluate @total each time
# through the loop in case the array has changed.
def each_index()
i = 0
while i < @total
yield i
i += 1
end
self
end
# Returns true if both are the same object or if both
# have the same elements (#eql? used for testing.)
def eql?(other)
return true if equal? other
return false unless other.kind_of?(Array)
return false if @total != other.size
each_with_index { |o, i| return false unless o.eql?(other[i]) }
true
end
# True if Array has no elements.
def empty?()
@total == 0
end
# Attempts to return the element at the given index. By default
# an IndexError is raised if the element is out of bounds. The
# user may supply either a default value or a block that takes
# the index object instead.
def fetch(idx, *rest)
raise ArgumentError, "Expected 1-2, got #{1 + rest.length}" if rest.length > 1
warn 'Block supercedes default object' if !rest.empty? && block_given?
idx, orig = Type.coerce_to(idx, Fixnum, :to_int), idx
idx += @total if idx < 0
if idx < 0 || idx >= @total
return yield(orig) if block_given?
return rest.at(0) unless rest.empty?
raise IndexError, "Index #{idx} out of array" if rest.empty?
end
at(idx)
end
# Fill some portion of the Array with a given element. The
# element to be used can be either given as the first argument
# or as a block that takes the index as its argument. The
# section that is to be filled can be defined by the following
# arguments. The first following argument is either a starting
# index or a Range. If the first argument is a starting index,
# the second argument can be the length. No length given
# defaults to rest of Array, no starting defaults to 0. Negative
# indices are treated as counting backwards from the end. Negative
# counts leave the Array unchanged. Returns self.
#
# array.fill(obj) -> array
# array.fill(obj, start [, length]) -> array
# array.fill(obj, range) -> array
# array.fill {|index| block } -> array
# array.fill(start [, length]) {|index| block } -> array
# array.fill(range) {|index| block } -> array
#
def fill(*args)
raise ArgumentError, "Wrong number of arguments" if block_given? and args.size > 2
raise ArgumentError, "Wrong number of arguments" if args.size > 3
# Normalise arguments
start, finish, obj = 0, (@total - 1), nil
obj = args.shift unless block_given?
one, two = args.at(0), args.at(1)
if one.kind_of? Range
raise TypeError, "Length invalid with range" if args.size > 1 # WTF, MRI, TypeError?
start, finish = Type.coerce_to(one.begin, Fixnum, :to_int), Type.coerce_to(one.end, Fixnum, :to_int)
start += @total if start < 0
finish += @total if finish < 0
if one.exclude_end?
return self if start == finish
finish -= 1
end
raise RangeError, "#{one.inspect} out of range" if start < 0
return self if finish < 0 # Nothing to modify
else
if one
start = Type.coerce_to one, Fixnum, :to_int
start += @total if start < 0
start = 0 if start < 0 # MRI comp adjusts to 0
if two
finish = Type.coerce_to two, Fixnum, :to_int
raise ArgumentError, "argument too big" if finish < 0 && start < finish.abs
return self if finish < 1 # Nothing to modify
finish = start + finish - 1
end
end
end # Argument normalisation
# Adjust the size progressively
unless finish < @total
nt = finish + 1
reallocate(nt) if @tuple.size < nt
@total = finish + 1
end
if block_given?
start.upto(finish) { |i| @tuple.put @start + i, yield(i) }
else
start.upto(finish) { |i| @tuple.put @start + i, obj }
end
self
end
# Returns the first or first n elements of the Array.
# If no argument is given, returns nil if the item
# is not found. If there is an argument, an empty
# Array is returned instead.
def first(n = nil)
return at(0) unless n
n = Type.coerce_to n, Fixnum, :to_int
raise ArgumentError, "Size must be positive" if n < 0
Array.new(self[0...n])
end
# Recursively flatten any contained Arrays into an one-dimensional result.
def flatten()
dup.flatten! || self
end
# Flattens self in place as #flatten. If no changes are
# made, returns nil, otherwise self.
def flatten!
ret, out = nil, []
ret = recursively_flatten(self, out)
replace(out) if ret
ret
end
# Computes a Fixnum hash code for this Array. Any two
# Arrays with the same content will have the same hash
# code (similar to #eql?)
def hash()
# IMPROVE: This is a really really poor implementation of hash for an array, but
# it does work. It should be replaced with something much better, but I'm not sure
# what level it belongs at.
str = ""
each { |item| str << item.hash.to_s }
str.hash
end
# Returns true if the given obj is present in the Array.
# Presence is determined by calling elem == obj until found.
def include?(obj)
@total.times { |i| return true if @tuple.at(@start + i) == obj }
false
end
# Returns the index of the first element in the Array
# for which elem == obj is true or nil.
def index(obj)
@total.times { |i| return i if @tuple.at(@start + i) == obj }
nil
end
# Returns an Array populated with the objects at the given indices of the original.
# Range arguments are given as nested Arrays as from #[].
def indexes(*args)
warn 'Array#indexes is deprecated, use Array#values_at instead'
out = []
args.each { |a|
if a.kind_of? Range
out << self[a]
else
out << at(Type.coerce_to(a, Fixnum, :to_int))
end
}
out
end
alias_method :indices, :indexes
# For a positive index, inserts the given values before
# the element at the given index. Negative indices count
# backwards from the end and the values are inserted
# after them.
def insert(idx, *items)
return self if items.length == 0
# Adjust the index for correct insertion
idx = Type.coerce_to idx, Fixnum, :to_int
idx += (@total + 1) if idx < 0 # Negatives add AFTER the element
raise IndexError, "#{idx} out of bounds" if idx < 0
self[idx, 0] = items # Cheat
self
end
# Produces a printable string of the Array. The string
# is constructed by calling #inspect on all elements.
# Descends through contained Arrays, recursive ones
# are indicated as [...].
def inspect()
return "[...]" if RecursionGuard.inspecting?(self)
out = []
RecursionGuard.inspect(self) do
each { |o|
out << o.inspect
}
end
"[#{out.join ', '}]"
end
# Generates a string from converting all elements of
# the Array to strings, inserting a separator between
# each. The separator defaults to $,. Detects recursive
# Arrays.
def join(sep = nil, method = :to_s)
return "" if @total == 0
sep ||= $,
begin
sep = sep.to_str
rescue NoMethodError
raise TypeError, "Cannot convert #{sep.inspect} to str"
end
out = ""
@total.times do |i|
elem = at(i)
out << sep unless i == 0
if elem.kind_of?(Array)
if RecursionGuard.inspecting?(elem)
out << "[...]"
else
RecursionGuard.inspect(self) do
out << elem.join(sep, method)
end
end
else
out << elem.__send__(method)
end
end
out
end
# Returns the last element or n elements of self. If
# the Array is empty, without a count nil is returned,
# otherwise an empty Array. Always returns an Array.
def last(n = nil)
return at(-1) unless n
n = Type.coerce_to n, Fixnum, :to_int
return [] if n.zero?
raise ArgumentError, "Number must be positive" if n < 0
n = size if n > size
Array.new self[-n..-1]
end
# Creates a new Array from the return values of passing
# each element in self to the supplied block.
def map()
out = []
each { |elem| out << yield(elem) }
out
end
alias_method :collect, :map
# Replaces each element in self with the return value
# of passing that element to the supplied block.
def map!(&block)
replace(map &block)
end
alias_method :collect!, :map!
# Returns number of non-nil elements in self, may be zero
def nitems
sum = 0
each { |elem| sum += 1 unless elem.nil? }
sum
end
BASE_64_ALPHA = {}
def self.after_loaded
(0..25).each {|x| BASE_64_ALPHA[x] = ?A + x}
(26..51).each {|x| BASE_64_ALPHA[x] = ?a + x - 26}
(52..61).each {|x| BASE_64_ALPHA[x] = ?0 + x - 52}
BASE_64_ALPHA[62] = ?+
BASE_64_ALPHA[63] = ?/
end
# TODO fill out pack.
def pack(schema)
# The schema is an array of arrays like [["A", "6"], ["u", "*"], ["X", ""]]. It represents the parsed
# form of "A6u*X".
# Remove strings in the schema between # and \n
schema = schema.gsub(/#[^\n]{0,}\n{0,1}/,'')
schema = schema.scan(/([^\s\d\*][\d\*]*)/).flatten.map {|x| x.match(/([^\s\d\*])([\d\*]*)/)[1..-1] }
# create the buffer
ret = ""
# we're starting from the first element in the array
arr_idx = 0
schema.each do |scheme|
# get the kind of pack
kind = scheme[0]
# get the array item being worked on
item = self[arr_idx]
# MRI nil compatibilty for string functions
item = "" if !item && kind =~ /[aAZbBhH]/
# set t to nil if no number (or "*") was passed in
t = scheme[1].empty? ? nil : scheme[1]
# X deletes a number of characters from the buffer (defaults to one; * means 0)
if kind == "X"
# set the default number to 1; otherwise to_i will give us the correct value
t = t.nil? ? 1 : t.to_i
# don't allow backing up farther than the size of the buffer
raise ArgumentError, "you're backing up too far" if t > ret.size
ret = ret[0..-(t + 1)]
# x returns just a group of null strings
elsif kind == "x"
size = t.nil? ? 1 : t.to_i
ret << "\x0" * size
# if there's no item, that means there's more schema items than array items,
# so throw an error. All actions that DON'T increment arr_idx must occur
# before this test.
elsif arr_idx >= self.length
raise ArgumentError, "too few array elements"
# TODO: Document this
elsif kind == "N"
obj = item
parts = []
4.times do
parts << (obj % 256)
obj = obj / 256
end
3.downto(0) do |j|
ret << parts[j].chr
end
arr_idx += 1
elsif kind == "V"
obj = item
parts = []
4.times do
parts << (obj % 256)
obj = obj / 256
end
0.upto(3) do |j|
ret << parts[j].chr
end
arr_idx += 1
# A and a both pad the text
elsif kind =~ /[aAZ]/
item = Type.coerce_to(item, String, :to_str)
# The total new string size will be:
# * the number passed in
# * the size of the array's string if "*" was passed in
# * 1 if nothing was passed in
size = !t ? 1 : (t == "*" ? item.size : t.to_i)
# Z has a twist: "*" adds a null to the end of the string
size += 1 if kind == "Z" && t == "*"
# Pad or truncate the string (with spaces) as appropriate
ret << ("%-#{size}s" % item.dup)[0...(size)]
# The padding size is the calculated size minus the string size
padsize = size - item.size
# Replace the space padding for null padding in "a" or "Z"
ret = ret.gsub(/\ {#{padsize}}$/, ("\x0" * (padsize)) ) if kind =~ /[aZ]/ && padsize > 0
arr_idx += 1
# b/B converts a binary string e.g. '1010101' into bytes
elsif kind =~ /[bB]/
item = Type.coerce_to(item, String, :to_str)
byte = 0
size = t.nil? ? 1 : (t == "*" ? item.length : t.to_i)
# shift lsb in from the left of string for b or msb for B
shift_in_lsb = (kind == "b")
0.upto(size > item.length ? item.length-1 : size-1) do |i|
bit = item[i] & 1
byte |= shift_in_lsb ? bit << (i & 7) : bit << (7 - (i & 7))
if (i & 7) == 7
ret << byte.chr
byte = 0
end