1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Services for byte streams and arrays
19 import collection
::array
20 intrude import text
::flat
22 # Any kind of entity which can be searched for in a Sequence of Byte
24 # Return the first occurence of `self` in `b`, or -1 if not found
25 fun first_index_in
(b
: SequenceRead[Byte]): Int do return first_index_in_from
(b
, 0)
27 # Return the first occurence of `self` in `b` starting at `from`, or -1 if not found
28 fun first_index_in_from
(b
: SequenceRead[Byte], from
: Int): Int is abstract
30 # Return the last occurence of `self` in `b`, or -1 if not found
31 fun last_index_in
(b
: SequenceRead[Byte]): Int do return last_index_in_from
(b
, b
.length
- 1)
33 # Return the last occurence of `self` in `b`, or -1 if not found
34 fun last_index_in_from
(b
: SequenceRead[Byte], from
: Int): Int is abstract
36 # Returns the indexes of all the occurences of `self` in `b`
37 fun search_all_in
(b
: SequenceRead[Byte]): SequenceRead[Int] is abstract
39 # Length of the pattern
40 fun pattern_length
: Int is abstract
42 # Appends `self` to `b`
43 fun append_to
(b
: Sequence[Byte]) is abstract
45 # Is `self` a prefix for `b` ?
46 fun is_prefix
(b
: SequenceRead[Byte]): Bool is abstract
48 # Is `self` a suffix for `b` ?
49 fun is_suffix
(b
: SequenceRead[Byte]): Bool is abstract
55 # Write self as a string into `ns` at position `pos`
56 private fun add_digest_at
(ns
: NativeString, pos
: Int) do
57 var tmp
= (0xF0u
8 & self) >> 4
58 ns
[pos
] = if tmp
>= 0x0Au
8 then tmp
+ 0x37u
8 else tmp
+ 0x30u
8
60 ns
[pos
+ 1] = if tmp
>= 0x0Au
8 then tmp
+ 0x37u
8 else tmp
+ 0x30u
8
63 # Is `self` a valid hexadecimal digit (in ASCII)
66 # intrude import core::bytes
67 # assert not '/'.ascii.is_valid_hexdigit
68 # assert '0'.ascii.is_valid_hexdigit
69 # assert '9'.ascii.is_valid_hexdigit
70 # assert not ':'.ascii.is_valid_hexdigit
71 # assert not '@'.ascii.is_valid_hexdigit
72 # assert 'A'.ascii.is_valid_hexdigit
73 # assert 'F'.ascii.is_valid_hexdigit
74 # assert not 'G'.ascii.is_valid_hexdigit
75 # assert not '`'.ascii.is_valid_hexdigit
76 # assert 'a'.ascii.is_valid_hexdigit
77 # assert 'f'.ascii.is_valid_hexdigit
78 # assert not 'g'.ascii.is_valid_hexdigit
80 private fun is_valid_hexdigit
: Bool do
81 return (self >= 0x30u
8 and self <= 0x39u
8) or
82 (self >= 0x41u
8 and self <= 0x46u
8) or
83 (self >= 0x61u
8 and self <= 0x66u
8)
86 # `self` as a hexdigit to its byte value
89 # intrude import core::bytes
90 # assert 0x39u8.hexdigit_to_byteval == 0x09u8
91 # assert 0x43u8.hexdigit_to_byteval == 0x0Cu8
94 # REQUIRE: `self.is_valid_hexdigit`
95 private fun hexdigit_to_byteval
: Byte do
96 if self >= 0x30u
8 and self <= 0x39u
8 then
98 else if self >= 0x41u
8 and self <= 0x46u
8 then
100 else if self >= 0x61u
8 and self <= 0x66u
8 then
103 # Happens only if the requirement is not met.
104 # i.e. this abort is here to please the compiler
108 redef fun first_index_in_from
(b
, from
) do
109 for i
in [from
.. b
.length
[ do if b
[i
] == self then return i
113 redef fun last_index_in_from
(b
, from
) do
114 for i
in [0 .. from
].step
(-1) do if b
[i
] == self then return i
118 redef fun search_all_in
(b
) do
119 var ret
= new Array[Int]
122 pos
= first_index_in_from
(b
, pos
)
123 if pos
== -1 then return ret
129 redef fun pattern_length
do return 1
131 redef fun append_to
(b
) do b
.push
self
133 # assert 'b'.ascii.is_suffix("baqsdb".to_bytes)
134 # assert not 'b'.ascii.is_suffix("baqsd".to_bytes)
135 redef fun is_suffix
(b
) do return b
.length
!= 0 and b
.last
== self
137 # assert 'b'.ascii.is_prefix("baqsdb".to_bytes)
138 # assert not 'b'.ascii.is_prefix("aqsdb".to_bytes)
139 redef fun is_prefix
(b
) do return b
.length
!= 0 and b
.first
== self
142 # A buffer containing Byte-manipulation facilities
144 # Uses Copy-On-Write when persisted
146 super AbstractArray[Byte]
149 # A NativeString being a char*, it can be used as underlying representation here.
150 var items
: NativeString
152 # Number of bytes in the array
155 # Capacity of the array
156 private var capacity
: Int
158 # Has this buffer been persisted (to_s'd)?
160 # Used for Copy-On-Write
161 private var persisted
= false
163 # var b = new Bytes.empty
164 # assert b.to_s == ""
166 var ns
= new NativeString(0)
170 # Init a `Bytes` with capacity `cap`
171 init with_capacity
(cap
: Int) do
172 var ns
= new NativeString(cap
)
176 redef fun pattern_length
do return length
178 redef fun is_empty
do return length
== 0
180 # var b = new Bytes.empty
182 # assert b[0] == 101u8
189 # Returns a copy of `self`
191 var b
= new Bytes.with_capacity
(length
)
196 # Trims off the whitespaces at the beginning and the end of `self`
198 # var b = "102041426E6F1020" .hexdigest_to_bytes
199 # assert b.trim.hexdigest == "41426E6F"
201 # NOTE: A whitespace is defined here as a byte whose value is <= 0x20
205 if self[st
] > 0x20u
8 then break
208 if st
>= length
then return new Bytes.empty
211 if self[ed
] > 0x20u
8 then break
214 return slice
(st
, ed
- st
+ 1)
217 # Returns a subset of the content of `self` starting at `from` and of length `count`
219 # var b = "abcd".to_bytes
220 # assert b.slice(1, 2).hexdigest == "6263"
221 # assert b.slice(-1, 2).hexdigest == "61"
222 # assert b.slice(1, 0).hexdigest == ""
223 # assert b.slice(2, 5).hexdigest == "6364"
224 fun slice
(from
, count
: Int): Bytes do
225 if count
<= 0 then return new Bytes.empty
229 if count
< 0 then count
= 0
233 if (count
+ from
) > length
then count
= length
- from
234 if count
<= 0 then return new Bytes.empty
236 var ret
= new Bytes.with_capacity
(count
)
238 ret
.append_ns
(items
.fast_cstring
(from
), count
)
242 # Returns a copy of `self` starting at `from`
244 # var b = "abcd".to_bytes
245 # assert b.slice_from(1).hexdigest == "626364"
246 # assert b.slice_from(-1).hexdigest == "61626364"
247 # assert b.slice_from(2).hexdigest == "6364"
248 fun slice_from
(from
: Int): Bytes do
249 if from
>= length
then return new Bytes.empty
250 if from
< 0 then from
= 0
251 return slice
(from
, length
)
254 # Returns self as an hexadecimal digest.
256 # Also known as plain hexdump or postscript hexdump.
259 # var b = "abcd".to_bytes
260 # assert b.hexdigest == "61626364"
261 # assert b.hexdigest.hexdigest_to_bytes == b
263 fun hexdigest
: String do
264 var elen
= length
* 2
265 var ns
= new NativeString(elen
)
269 self[i
].add_digest_at
(ns
, oi
)
273 return new FlatString.full
(ns
, elen
, 0, elen
)
276 # Return self as a C hexadecimal digest where bytes are prefixed by `\x`
278 # The output is compatible with literal stream of bytes for most languages
279 # including C and Nit.
282 # var b = "abcd".to_bytes
283 # assert b.chexdigest == "\\x61\\x62\\x63\\x64"
284 # assert b.chexdigest.unescape_to_bytes == b
286 fun chexdigest
: String do
287 var elen
= length
* 4
288 var ns
= new NativeString(elen
)
292 ns
[oi
] = 0x5Cu
8 # b'\\'
293 ns
[oi
+1] = 0x78u
8 # b'x'
294 self[i
].add_digest_at
(ns
, oi
+2)
298 return new FlatString.full
(ns
, elen
, 0, elen
)
302 # Returns self as a stream of bits (0 and 1)
305 # var b = "abcd".to_bytes
306 # assert b.binarydigest == "01100001011000100110001101100100"
307 # assert b.binarydigest.binarydigest_to_bytes == b
309 fun binarydigest
: String do
310 var elen
= length
* 8
311 var ns
= new NativeString(elen
)
319 ns
[oi
] = 0x30u
8 # b'0'
321 ns
[oi
] = 0x31u
8 # b'1'
328 return new FlatString.full
(ns
, elen
, 0, elen
)
331 # var b = new Bytes.with_capacity(1)
333 # assert b.to_s == "e"
334 redef fun []=(i
, v
) do
335 if persisted
then regen
338 if i
== length
then add
(v
)
342 # var b = new Bytes.empty
344 # assert b.to_s == "e"
346 if persisted
then regen
347 if length
>= capacity
then
354 # Adds the UTF-8 representation of `c` to `self`
356 # var b = new Bytes.empty
359 # assert b.hexdigest == "41E382AD"
360 fun add_char
(c
: Char) do
361 if persisted
then regen
362 var cln
= c
.u8char_len
365 items
.set_char_at
(length
, c
)
369 # var b = new Bytes.empty
370 # b.append([104u8, 101u8, 108u8, 108u8, 111u8])
371 # assert b.to_s == "hello"
372 redef fun append
(arr
) do
373 if arr
isa Bytes then
374 append_ns
(arr
.items
, arr
.length
)
376 for i
in arr
do add i
380 # var b = new Bytes.empty
381 # b.append([0x41u8, 0x41u8, 0x18u8])
383 # assert b.to_s == "AA"
390 redef fun clear
do length
= 0
392 # Regenerates the buffer, necessary when it was persisted
394 var nns
= new NativeString(capacity
)
395 items
.copy_to
(nns
, length
, 0, 0)
399 # Appends the `ln` first bytes of `ns` to self
400 fun append_ns
(ns
: NativeString, ln
: Int) do
401 if persisted
then regen
402 var nlen
= length
+ ln
403 if nlen
> capacity
then enlarge
(nlen
)
404 ns
.copy_to
(items
, ln
, 0, length
)
408 # Appends `ln` bytes from `ns` starting at index `from` to self
409 fun append_ns_from
(ns
: NativeString, ln
, from
: Int) do
410 if persisted
then regen
411 var nlen
= length
+ ln
412 if nlen
> capacity
then enlarge
(nlen
)
413 ns
.copy_to
(items
, ln
, from
, length
)
417 # Appends the bytes of `s` to `selftextextt`
418 fun append_text
(s
: Text) do
419 for i
in s
.substrings
do
420 append_ns
(i
.fast_cstring
, i
.bytelen
)
424 redef fun append_to
(b
) do b
.append
self
426 redef fun enlarge
(sz
) do
427 if capacity
>= sz
then return
429 while capacity
< sz
do capacity
= capacity
* 2 + 2
430 var ns
= new NativeString(capacity
)
431 items
.copy_to
(ns
, length
, 0, 0)
438 var r
= b
.items
.to_s_with_length
(length
)
439 if r
!= items
then persisted
= false
443 redef fun iterator
do return new BytesIterator.with_buffer
(self)
445 redef fun first_index_in_from
(b
, from
) do
446 if is_empty
then return -1
448 var bpos
= fst
.first_index_in_from
(self, from
)
449 for i
in [0 .. length
[ do
450 if self[i
] != b
[bpos
] then return first_index_in_from
(b
, bpos
+ 1)
456 redef fun last_index_in_from
(b
, from
) do
457 if is_empty
then return -1
458 var lst
= self[length
- 1]
459 var bpos
= lst
.last_index_in_from
(b
, from
)
460 for i
in [0 .. length
[.step
(-1) do
461 if self[i
] != b
[bpos
] then return last_index_in_from
(b
, bpos
- 1)
467 redef fun search_all_in
(b
) do
468 var ret
= new Array[Int]
469 var pos
= first_index_in_from
(b
, 0)
470 if pos
== -1 then return ret
474 pos
= first_index_in_from
(b
, pos
)
475 if pos
== -1 then return ret
481 # Splits the content on self when encountering `b`
483 # var a = "String is string".to_bytes.split_with('s'.ascii)
484 # assert a.length == 3
485 # assert a[0].hexdigest == "537472696E672069"
486 # assert a[1].hexdigest == "20"
487 # assert a[2].hexdigest == "7472696E67"
488 fun split_with
(b
: BytePattern): Array[Bytes] do
489 var fst
= b
.search_all_in
(self)
490 if fst
.is_empty
then return [clone
]
491 var retarr
= new Array[Bytes]
494 retarr
.add
(slice
(prev
, i
- prev
))
495 prev
= i
+ b
.pattern_length
497 retarr
.add slice_from
(prev
)
501 # Splits `self` in two parts at the first occurence of `b`
503 # var a = "String is string".to_bytes.split_once_on('s'.ascii)
504 # assert a[0].hexdigest == "537472696E672069"
505 # assert a[1].hexdigest == "20737472696E67"
506 fun split_once_on
(b
: BytePattern): Array[Bytes] do
507 var spl
= b
.first_index_in
(self)
508 if spl
== -1 then return [clone
]
509 var ret
= new Array[Bytes].with_capacity
(2)
510 ret
.add
(slice
(0, spl
))
511 ret
.add
(slice_from
(spl
+ b
.pattern_length
))
515 # Replaces all the occurences of `this` in `self` by `by`
517 # var b = "String is string".to_bytes.replace(0x20u8, 0x41u8)
518 # assert b.hexdigest == "537472696E6741697341737472696E67"
519 fun replace
(pattern
: BytePattern, bytes
: BytePattern): Bytes do
520 if is_empty
then return new Bytes.empty
521 var pos
= pattern
.search_all_in
(self)
522 if pos
.is_empty
then return clone
523 var ret
= new Bytes.with_capacity
(length
)
526 ret
.append_ns
(items
.fast_cstring
(prev
), i
- prev
)
528 prev
= i
+ pattern
.pattern_length
530 ret
.append
(slice_from
(pos
.last
+ pattern
.pattern_length
))
534 # Decode `self` from percent (or URL) encoding to a clear string
536 # Replace invalid use of '%' with '?'.
538 # assert "aBc09-._~".to_bytes.from_percent_encoding == "aBc09-._~".to_bytes
539 # assert "%25%28%29%3c%20%3e".to_bytes.from_percent_encoding == "%()< >".to_bytes
540 # assert ".com%2fpost%3fe%3dasdf%26f%3d123".to_bytes.from_percent_encoding == ".com/post?e=asdf&f=123".to_bytes
541 # assert "%25%28%29%3C%20%3E".to_bytes.from_percent_encoding == "%()< >".to_bytes
542 # assert "incomplete %".to_bytes.from_percent_encoding == "incomplete ?".to_bytes
543 # assert "invalid % usage".to_bytes.from_percent_encoding == "invalid ? usage".to_bytes
544 # assert "%c3%a9%e3%81%82%e3%81%84%e3%81%86".to_bytes.from_percent_encoding == "éあいう".to_bytes
545 fun from_percent_encoding
: Bytes do
546 var tmp
= new Bytes.with_capacity
(length
)
548 while pos
< length
do
550 if b
!= '%'.ascii
then
555 if length
- pos
< 2 then
560 var bn
= self[pos
+ 1]
561 var bnn
= self[pos
+ 2]
562 if not bn
.is_valid_hexdigit
or not bnn
.is_valid_hexdigit
then
567 tmp
.add
((bn
.hexdigit_to_byteval
<< 4) + bnn
.hexdigit_to_byteval
)
573 # Is `b` a prefix of `self` ?
574 fun has_prefix
(b
: BytePattern): Bool do return b
.is_prefix
(self)
576 # Is `b` a suffix of `self` ?
577 fun has_suffix
(b
: BytePattern): Bool do return b
.is_suffix
(self)
579 redef fun is_suffix
(b
) do
580 if length
> b
.length
then return false
584 if self[i
] != b
[j
] then return false
591 redef fun is_prefix
(b
) do
592 if length
> b
.length
then return false
593 for i
in [0 .. length
[ do if self[i
] != b
[i
] then return false
598 private class BytesIterator
599 super IndexedIterator[Byte]
601 var tgt
: NativeString
607 init with_buffer
(b
: Bytes) do init(b
.items
, 0, b
.length
)
609 redef fun is_ok
do return index
< max
611 redef fun next
do index
+= 1
613 redef fun item
do return tgt
[index
]
617 # Returns a mutable copy of `self`'s bytes
620 # assert "String".to_bytes isa Bytes
621 # assert "String".to_bytes == [83u8, 116u8, 114u8, 105u8, 110u8, 103u8]
623 fun to_bytes
: Bytes do
624 var b
= new Bytes.with_capacity
(bytelen
)
629 # Is `self` a valid hexdigest ?
631 # assert "0B1d3F".is_valid_hexdigest
632 # assert not "5G".is_valid_hexdigest
633 fun is_valid_hexdigest
: Bool do
634 for i
in bytes
do if not i
.is_valid_hexdigit
then return false
638 # Appends `self.bytes` to `b`
639 fun append_to_bytes
(b
: Bytes) do
640 for s
in substrings
do
641 var from
= if s
isa FlatString then s
.first_byte
else 0
642 b
.append_ns_from
(s
.items
, s
.bytelen
, from
)
646 # Returns a new `Bytes` instance with the digest as content
648 # assert "0B1F4D".hexdigest_to_bytes == [0x0Bu8, 0x1Fu8, 0x4Du8]
649 # assert "0B1F4D".hexdigest_to_bytes.hexdigest == "0B1F4D"
651 # Characters that are not hexadecimal digits are ignored.
653 # assert "z0B1 F4\nD".hexdigest_to_bytes.hexdigest == "0B1F4D"
654 # assert "\\x0b1 \\xf4d".hexdigest_to_bytes.hexdigest == "0B1F4D"
656 # When the number of hexadecimal digit is not even, then a leading 0 is
657 # implicitly considered to fill the left byte (the most significant one).
659 # assert "1".hexdigest_to_bytes.hexdigest == "01"
660 # assert "FFF".hexdigest_to_bytes.hexdigest == "0FFF"
662 # `Bytes::hexdigest` is a loosely reverse method since its
663 # results contain only pairs of uppercase hexadecimal digits.
665 # assert "ABCD".hexdigest_to_bytes.hexdigest == "ABCD"
666 # assert "a b c".hexdigest_to_bytes.hexdigest == "0ABC"
667 fun hexdigest_to_bytes
: Bytes do
671 var dlength
= 0 # Number of hex digits
675 if c
.is_valid_hexdigit
then dlength
+= 1
679 # Allocate the result buffer
680 var ret
= new Bytes.with_capacity
((dlength
+1) / 2)
682 var i
= (dlength
+1) % 2 # current hex digit (1=high, 0=low)
683 var byte
= 0u8
# current accumulated byte value
688 if c
.is_valid_hexdigit
then
689 byte
= byte
<< 4 | c
.hexdigit_to_byteval
692 # Last digit known: store and restart
703 # Gets the hexdigest of the bytes of `self`
705 # assert "<STRING/&rt;".hexdigest == "266C743B535452494E47262334373B2672743B"
706 fun hexdigest
: String do
708 var outns
= new NativeString(ln
* 2)
710 for i
in [0 .. ln
[ do
711 bytes
[i
].add_digest_at
(outns
, oi
)
714 return new FlatString.with_infos
(outns
, ln
* 2, 0)
717 # Return a `Bytes` instance where Nit escape sequences are transformed.
719 # assert "B\\n\\x41\\u0103D3".unescape_to_bytes.hexdigest == "420A41F0908F93"
721 # `Bytes::chexdigest` is a loosely reverse methods since its result is only made
722 # of `"\x??"` escape sequences.
724 # assert "\\x41\\x42\\x43".unescape_to_bytes.chexdigest == "\\x41\\x42\\x43"
725 # assert "B\\n\\x41\\u0103D3".unescape_to_bytes.chexdigest == "\\x42\\x0A\\x41\\xF0\\x90\\x8F\\x93"
726 fun unescape_to_bytes
: Bytes do
727 var res
= new Bytes.with_capacity
(self.bytelen
)
728 var was_slash
= false
732 if not was_slash
then
744 else if c
== 'r' then
746 else if c
== 't' then
748 else if c
== '0' then
750 else if c
== 'x' or c
== 'X' then
751 var hx
= substring
(i
+ 1, 2)
753 res
.add
(hx
.to_hex
.to_b
)
758 else if c
== 'u' or c
== 'U' then
759 var hx
= substring
(i
+ 1, 6)
761 res
.add_char
(hx
.to_hex
.code_point
)
774 # Return a `Bytes` by reading 0 and 1.
776 # assert "1010101100001101".binarydigest_to_bytes.hexdigest == "AB0D"
778 # Note that characters that are neither 0 or 1 are just ignored.
780 # assert "a1B01 010\n1100あ001101".binarydigest_to_bytes.hexdigest == "AB0D"
781 # assert "hello".binarydigest_to_bytes.is_empty
783 # When the number of bits is not divisible by 8, then leading 0 are
784 # implicitly considered to fill the left byte (the most significant one).
786 # assert "1".binarydigest_to_bytes.hexdigest == "01"
787 # assert "1111111".binarydigest_to_bytes.hexdigest == "7F"
788 # assert "1000110100".binarydigest_to_bytes.hexdigest == "0234"
790 # `Bytes::binarydigest` is a loosely reverse method since its
791 # results contain only 1 and 0 by blocks of 8.
793 # assert "1010101100001101".binarydigest_to_bytes.binarydigest == "1010101100001101"
794 # assert "1".binarydigest_to_bytes.binarydigest == "00000001"
795 fun binarydigest_to_bytes
: Bytes
806 if c
== 0x30u
8 or c
== 0x31u
8 then bitlen
+= 1 # b'0' or b'1'
809 # Allocate (and take care of the padding)
810 var ret
= new Bytes.with_capacity
((bitlen
+7) / 8)
812 var i
= (bitlen
+7) % 8 # current bit (7th=128, 0th=1)
813 var byte
= 0u8
# current accumulated byte value
819 if c
== 0x30u
8 then # b'0'
821 else if c
== 0x31u
8 then # b'1'
822 byte
= byte
<< 1 | 1u8
829 # Last bit known: store and restart
840 redef fun append_to_bytes
(b
) do
841 var from
= if self isa FlatString then first_byte
else 0
842 b
.append_ns_from
(items
, bytelen
, from
)
846 redef class NativeString
847 # Creates a new `Bytes` object from `self` with `len` as length
849 # If `len` is null, strlen will determine the length of the Bytes
850 fun to_bytes
(len
: nullable Int): Bytes do
851 if len
== null then len
= cstring_length
852 return new Bytes(self, len
, len
)
855 # Creates a new `Bytes` object from a copy of `self` with `len` as length
857 # If `len` is null, strlen will determine the length of the Bytes
858 fun to_bytes_with_copy
(len
: nullable Int): Bytes do
859 if len
== null then len
= cstring_length
860 var nns
= new NativeString(len
)
861 copy_to
(nns
, len
, 0, 0)
862 return new Bytes(nns
, len
, len
)
866 # Joins an array of bytes `arr` separated by `sep`
868 # assert join_bytes(["String".to_bytes, "is".to_bytes, "string".to_bytes], ' '.ascii).hexdigest == "537472696E6720697320737472696E67"
869 fun join_bytes
(arr
: Array[Bytes], sep
: nullable BytePattern): Bytes do
870 if arr
.is_empty
then return new Bytes.empty
871 sep
= sep
or else new Bytes.empty
872 var endln
= sep
.pattern_length
* (arr
.length
- 1)
873 for i
in arr
do endln
+= i
.length
874 var ret
= new Bytes.with_capacity
(endln
)
875 ret
.append
(arr
.first
)
876 for i
in [1 .. arr
.length
[ do