Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is intentional to disable guttering which is duplicate of Run tests| Update tests

"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