Skip to content

Commit

Permalink
hongbo/string rev iter (#886)
Browse files Browse the repository at this point in the history
* disable gutte

* add String::rev_iter

* tweak
  • Loading branch information
bobzhang authored Aug 22, 2024
1 parent cb3a465 commit 34096f4
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"testing.gutterEnabled": false
}
55 changes: 55 additions & 0 deletions string/string.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,61 @@ pub fn iter(self : String) -> Iter[Char] {
)
}

/// Returns an iterator that yields characters from the end to the start of the string. This function handles
/// Unicode surrogate pairs correctly, ensuring that characters are not split across surrogate pairs.
///
/// # Parameters
///
/// - `self` : The input `String` to be iterated in reverse.
///
/// # Returns
///
/// - An `Iter[Char]` that yields characters from the end to the start of the string.
///
/// # Behavior
///
/// - The function iterates over the string in reverse order.
/// - If a trailing surrogate is encountered, it checks for a preceding leading surrogate to form a complete Unicode code point.
/// - Yields each character or combined code point to the iterator.
/// - Stops iteration if the `yield` function returns `IterEnd`.
///
/// # Examples
///
/// ```moonbit
/// test {
/// let input = "Hello, World!"
/// let reversed = rev_iter(input).collect()
/// inspect!(reversed, content="[\"!\", \"d\", \"l\", \"r\", \"o\", \"W\", \" \", \",\", \"o\", \"l\", \"l\", \"e\", \"H\"]")
/// }
/// ```
pub fn rev_iter(self : String) -> Iter[Char] {
Iter::new(
fn(yield) {
let len = self.length()
for index = len - 1; index >= 0; index = index - 1 {
let c1 = self[index]
if is_trailing_surrogate(c1) && index - 1 >= 0 {
let c2 = self[index - 1]
if is_leading_surrogate(c2) {
let c = code_point_of_surrogate_pair(c2, c1)
if yield(c) == IterContinue {
continue index - 2
} else {
break IterEnd
}
}
}
// TODO: handle garbage input
if yield(c1) == IterEnd {
break IterEnd
}
} else {
IterContinue
}
},
)
}

/// Removes all leading and trailing spaces.
pub fn trim_space(self : String) -> String {
self.trim(" \n\r\t")
Expand Down
1 change: 1 addition & 0 deletions string/string.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl String {
replace(String, ~old : String, ~new : String) -> String
replace_all(String, ~old : String, ~new : String) -> String
rev(String) -> String
rev_iter(String) -> Iter[Char]
split(String, String) -> Iter[String]
starts_with(String, String) -> Bool
substring(String, ~start : Int = .., ~end : Int = ..) -> String
Expand Down
29 changes: 29 additions & 0 deletions string/string_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,35 @@ test "chars" {
)
}

test "rev_iter" {
let mut str = ""
"A😊𠮷BA😊𠮷B"
.rev_iter()
.each(fn(c) { str = str + c.to_int().to_string() + "\n" })
inspect!(
str,
content=
#|66
#|134071
#|128522
#|65
#|66
#|134071
#|128522
#|65
#|
,
)
inspect!(
"A😊𠮷BA😊𠮷B".rev_iter().take(2).collect(),
content="['B', '𠮷']",
)
inspect!(
"A😊𠮷BA😊𠮷B".rev_iter().take(4).collect(),
content="['B', '𠮷', '😊', 'A']",
)
}

// test here to avlid cyclic dependency between assertion and builtin
test "Buffer::to_bytes" {
let buffer = Buffer::new(size_hint=16)
Expand Down

0 comments on commit 34096f4

Please sign in to comment.