iter.ha (3475B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bytes; 5 use strings; 6 7 export type iterator = struct { 8 cur: []u8, 9 reverse: bool, 10 }; 11 12 // Returns an [[iterator]] which yields each component of a path, moving down 13 // through child dirs. If the path is absolute, the first component will be 14 // the root. The iterator can be copied to save its state. 15 export fn iter(buf: *buffer) iterator = iterator { 16 cur = buf.buf[..buf.end], 17 reverse = false, 18 }; 19 20 // Returns an [[iterator]] which yields each component of a path, moving up 21 // through parent dirs. If the path is absolute, the last component will be 22 // the root. The iterator can be copied to save its state. 23 export fn riter(buf: *buffer) iterator = iterator { 24 cur = buf.buf[..buf.end], 25 reverse = true, 26 }; 27 28 29 // Returns the next path component from an [[iterator]], or void if none 30 // remain. Does not advance the iterator. 31 export fn peekiter(it: *iterator) (str | void) = { 32 if (len(it.cur) == 0) return void; 33 const (result, remaining) = split_iter(it); 34 return strings::fromutf8_unsafe(result); 35 }; 36 37 // Returns the next path component from an [[iterator]], or done if none 38 // remain. Advances the iterator. 39 export fn nextiter(it: *iterator) (str | done) = { 40 if (len(it.cur) == 0) return done; 41 const (result, remaining) = split_iter(it); 42 it.cur = remaining; 43 return strings::fromutf8_unsafe(result); 44 }; 45 46 // helper function for nextiter and peekiter, returns (result, remaining) 47 fn split_iter(it: *iterator) ([]u8, []u8) = { 48 if (it.reverse) { 49 match (bytes::rindex(it.cur, SEP)) { 50 case let sep: size => 51 let res = it.cur[sep+1..]; 52 if (sep == 0) { 53 if (len(it.cur) == 1) { 54 res = it.cur; // return the root dir 55 } else { 56 sep = 1; // leave the root for next 57 }; 58 }; 59 return (res, it.cur[..sep]); 60 case void => 61 return (it.cur, it.cur[..0]); 62 }; 63 } else { 64 match (bytes::index(it.cur, SEP)) { 65 case let i: size => 66 return (it.cur[..if (i == 0) 1 else i], it.cur[i+1..]); 67 case void => 68 return (it.cur, it.cur[..0]); 69 }; 70 }; 71 }; 72 73 // Gets the remaining path from an iterator, without advancing the iterator. 74 export fn iterrem(it: *iterator) str = strings::fromutf8_unsafe(it.cur); 75 76 @test fn iter() void = { 77 const buf = init(local("/foo/bar/baz"))!; 78 let i = iter(&buf); 79 assert(nextiter(&i) as str == local("/")); 80 assert(nextiter(&i) as str == "foo"); 81 assert(nextiter(&i) as str == "bar"); 82 assert(nextiter(&i) as str == "baz"); 83 assert(nextiter(&i) is done); 84 i = riter(&buf); 85 assert(nextiter(&i) as str == "baz"); 86 assert(nextiter(&i) as str == "bar"); 87 assert(nextiter(&i) as str == "foo"); 88 assert(nextiter(&i) as str == local("/")); 89 assert(nextiter(&i) is done); 90 91 set(&buf, local("foo/bar/baz"))!; 92 i = iter(&buf); 93 assert(nextiter(&i) as str == "foo"); 94 assert(nextiter(&i) as str == "bar"); 95 assert(nextiter(&i) as str == "baz"); 96 assert(nextiter(&i) is done); 97 i = riter(&buf); 98 assert(nextiter(&i) as str == "baz"); 99 assert(nextiter(&i) as str == "bar"); 100 assert(nextiter(&i) as str == "foo"); 101 assert(nextiter(&i) is done); 102 103 set(&buf, "foo")!; 104 i = iter(&buf); 105 assert(nextiter(&i) as str == "foo"); 106 assert(nextiter(&i) is done); 107 i = riter(&buf); 108 assert(nextiter(&i) as str == "foo"); 109 assert(nextiter(&i) is done); 110 111 set(&buf, local("/"))!; 112 i = iter(&buf); 113 assert(nextiter(&i) as str == local("/")); 114 assert(nextiter(&i) is done); 115 i = riter(&buf); 116 assert(nextiter(&i) as str == local("/")); 117 assert(nextiter(&i) is done); 118 };