Skip to content

Commit 34096f4

Browse files
authored
hongbo/string rev iter (#886)
* disable gutte * add String::rev_iter * tweak
1 parent cb3a465 commit 34096f4

File tree

4 files changed

+88
-0
lines changed

4 files changed

+88
-0
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"testing.gutterEnabled": false
3+
}

string/string.mbt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,61 @@ pub fn iter(self : String) -> Iter[Char] {
116116
)
117117
}
118118

119+
/// Returns an iterator that yields characters from the end to the start of the string. This function handles
120+
/// Unicode surrogate pairs correctly, ensuring that characters are not split across surrogate pairs.
121+
///
122+
/// # Parameters
123+
///
124+
/// - `self` : The input `String` to be iterated in reverse.
125+
///
126+
/// # Returns
127+
///
128+
/// - An `Iter[Char]` that yields characters from the end to the start of the string.
129+
///
130+
/// # Behavior
131+
///
132+
/// - The function iterates over the string in reverse order.
133+
/// - If a trailing surrogate is encountered, it checks for a preceding leading surrogate to form a complete Unicode code point.
134+
/// - Yields each character or combined code point to the iterator.
135+
/// - Stops iteration if the `yield` function returns `IterEnd`.
136+
///
137+
/// # Examples
138+
///
139+
/// ```moonbit
140+
/// test {
141+
/// let input = "Hello, World!"
142+
/// let reversed = rev_iter(input).collect()
143+
/// inspect!(reversed, content="[\"!\", \"d\", \"l\", \"r\", \"o\", \"W\", \" \", \",\", \"o\", \"l\", \"l\", \"e\", \"H\"]")
144+
/// }
145+
/// ```
146+
pub fn rev_iter(self : String) -> Iter[Char] {
147+
Iter::new(
148+
fn(yield) {
149+
let len = self.length()
150+
for index = len - 1; index >= 0; index = index - 1 {
151+
let c1 = self[index]
152+
if is_trailing_surrogate(c1) && index - 1 >= 0 {
153+
let c2 = self[index - 1]
154+
if is_leading_surrogate(c2) {
155+
let c = code_point_of_surrogate_pair(c2, c1)
156+
if yield(c) == IterContinue {
157+
continue index - 2
158+
} else {
159+
break IterEnd
160+
}
161+
}
162+
}
163+
// TODO: handle garbage input
164+
if yield(c1) == IterEnd {
165+
break IterEnd
166+
}
167+
} else {
168+
IterContinue
169+
}
170+
},
171+
)
172+
}
173+
119174
/// Removes all leading and trailing spaces.
120175
pub fn trim_space(self : String) -> String {
121176
self.trim(" \n\r\t")

string/string.mbti

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ impl String {
1717
replace(String, ~old : String, ~new : String) -> String
1818
replace_all(String, ~old : String, ~new : String) -> String
1919
rev(String) -> String
20+
rev_iter(String) -> Iter[Char]
2021
split(String, String) -> Iter[String]
2122
starts_with(String, String) -> Bool
2223
substring(String, ~start : Int = .., ~end : Int = ..) -> String

string/string_test.mbt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,35 @@ test "chars" {
9999
)
100100
}
101101

102+
test "rev_iter" {
103+
let mut str = ""
104+
"A😊𠮷BA😊𠮷B"
105+
.rev_iter()
106+
.each(fn(c) { str = str + c.to_int().to_string() + "\n" })
107+
inspect!(
108+
str,
109+
content=
110+
#|66
111+
#|134071
112+
#|128522
113+
#|65
114+
#|66
115+
#|134071
116+
#|128522
117+
#|65
118+
#|
119+
,
120+
)
121+
inspect!(
122+
"A😊𠮷BA😊𠮷B".rev_iter().take(2).collect(),
123+
content="['B', '𠮷']",
124+
)
125+
inspect!(
126+
"A😊𠮷BA😊𠮷B".rev_iter().take(4).collect(),
127+
content="['B', '𠮷', '😊', 'A']",
128+
)
129+
}
130+
102131
// test here to avlid cyclic dependency between assertion and builtin
103132
test "Buffer::to_bytes" {
104133
let buffer = Buffer::new(size_hint=16)

0 commit comments

Comments
 (0)