///|
test "string creation" {
// From character array
let chars : ReadOnlyArray[Char] = ['H', 'e', 'l', 'l', 'o']
let str1 = String::from_array(chars)
inspect(str1, content="Hello")
// From character iterator
let str2 = String::from_iter(['W', 'o', 'r', 'l', 'd'].iter())
inspect(str2, content="World")
// Default empty string
let empty = ""
inspect(empty, content="")
}///|
test "string iteration" {
let text = "Hello🌍"
// Forward iteration
let chars = text.iter().collect()
debug_inspect(chars, content="['H', 'e', 'l', 'l', 'o', '🌍']")
// Reverse iteration
let reversed = text.rev_iter().collect()
debug_inspect(reversed, content="['🌍', 'o', 'l', 'l', 'e', 'H']")
// Iteration with indices - demonstrate iter2 functionality
let mut count = 0
let mut first_char = 'a'
text
.iter2()
.each(fn(idx, char) {
if idx == 0 {
first_char = char
}
count = count + 1
})
inspect(first_char, content="H")
inspect(count, content="6") // 6 Unicode characters
}///|
test "string conversion" {
let text = "Hello 你好"
// Convert to character array
let chars = text.to_array()
debug_inspect(chars, content="['H', 'e', 'l', 'l', 'o', ' ', '你', '好']")
// Convert to bytes (UTF-8 encoding)
let bytes = @utf8.encode(text) // Use UTF-8 encoding
inspect(bytes.length(), content="12")
debug_inspect(chars, content="['H', 'e', 'l', 'l', 'o', ' ', '你', '好']")
// Convert to bytes (UTF-16 LE encoding)
let bytes = @utf16.encode(text)
inspect(bytes.length(), content="16") // 5 chars * 2 bytes each
}///|
test "unicode handling" {
let emoji_text = "Hello🤣World"
// Character count vs UTF-16 code unit count
let char_count = emoji_text.iter().count()
let code_unit_count = emoji_text.length()
inspect(char_count, content="11") // Unicode characters
inspect(code_unit_count, content="12") // UTF-16 code units
// Find character offset
let offset = emoji_text.offset_of_nth_char(5) // Position of emoji
debug_inspect(offset, content="Some(5)")
// Test character length
let has_11_chars = emoji_text.char_length_eq(11)
inspect(has_11_chars, content="true")
}///|
test "string comparison" {
let result1 = "apple".compare("banana")
inspect(result1, content="-1") // apple < banana
let result2 = "hello".compare("hello")
inspect(result2, content="0") // equal
let result3 = "zebra".compare("apple")
inspect(result3, content="1") // zebra > apple
}direction: right
str: "String \"a😀b\" — UTF-16 code units" {
u0: "[0] 'a'"
u1: "[1] 0xD83D high surrogate"
u2: "[2] 0xDE00 low surrogate"
u3: "[3] 'b'"
}
view: "StringView {str, start, end}\ns[1:3] is the full 😀 (ok)\ns[2:4] starts inside 😀 → panics"
view -> str: "zero copy, indices are code units"///|
test "string views" {
let text = "Hello, World!"
let view = text[:][7:12] // "World" - create view using slice notation
// Views support similar operations as strings
let chars = view.iter().collect()
debug_inspect(chars, content="['W', 'o', 'r', 'l', 'd']")
// Convert view back to string
let substring = view.to_owned()
inspect(substring, content="World")
}///|
test "practical examples" {
let text = "The quick brown fox"
// Split into words (using whitespace) - returns Iter[View]
let words = text.split(" ").collect()
inspect(words.length(), content="4")
inspect(words[0].to_owned(), content="The")
inspect(words[3].to_owned(), content="fox")
// Join words back together - convert views to strings first
let word_strings = words.map(fn(v) { v.to_owned() })
let mut result = ""
for i, word in word_strings.iter2() {
if i > 0 {
result = result + "-"
}
result = result + word
}
inspect(result, content="The-quick-brown-fox")
// Case conversion (works on views)
let upper = text[:].to_upper().to_owned()
inspect(upper, content="THE QUICK BROWN FOX")
let lower = text[:].to_lower().to_owned()
inspect(lower, content="the quick brown fox")
}///|
test "string regex basics" {
let regex =
guard regex.execute("id=42") is Some(m) else { fail("Expected match") }
inspect(m.content(), content="42")
let replaced = regex.replace_by("a1b22", _m => "#")
inspect(replaced, content="a#b#")
let replaced_limited = regex.replace_by("a1b22c333", _m => "#", limit=2)
inspect(replaced_limited, content="a#b#c333")
}///|
test "from_str" {
let i : Int = @string.from_str("42")
inspect(i, content="42")
let negative : Int = @string.from_str("-17")
inspect(negative, content="-17")
let u : UInt = @string.from_str("42")
inspect(u, content="42")
let i64 : Int64 = @string.from_str("9999999999")
inspect(i64, content="9999999999")
let u64 : UInt64 = @string.from_str("18446744073709551615")
inspect(u64, content="18446744073709551615")
let d : Double = @string.from_str("3.14")
inspect(d, content="3.14")
let b : Bool = @string.from_str("true")
inspect(b, content="true")
}///|
test "parse integers with base" {
inspect(@string.parse_int("ff", base=16), content="255")
inspect(@string.parse_int("101", base=2), content="5")
inspect(@string.parse_uint64("ff_ff", base=16), content="65535")
}///|
test "match result" {
let re =
guard re.execute("key=42") is Some(m) else { fail("no match") }
inspect(m.content(), content="key=42")
inspect(m.before(), content="")
inspect(m.after(), content="")
debug_inspect(m.group(1), content="Some(<StringView: \"key\">)")
debug_inspect(m.group(2), content="Some(<StringView: \"42\">)")
}///|
test "named groups" {
let re =
guard re.execute("age:30") is Some(m) else { fail("no match") }
debug_inspect(m.named_group("name"), content="Some(<StringView: \"age\">)")
debug_inspect(m.named_group("val"), content="Some(<StringView: \"30\">)")
}///|
test "find and split" {
let digits =
let matches = digits
.find("a1b22c333")
.map(fn(m) { m.content().to_owned() })
.collect()
debug_inspect(
matches,
content=(
#|["1", "22", "333"]
),
)
let parts = digits.split("a1b22c333").map(fn(v) { v.to_owned() }).collect()
debug_inspect(
parts,
content=(
#|["a", "b", "c", ""]
),
)
}///|
test "regex combinators" {
// match "abc" literally
let abc = @string.Regex::string("abc")
inspect(abc.execute("xabcy") is Some(_), content="true")
// repeat: match 2 to 4 digits
let digits = .repeat(min=2, max=4)
guard digits.execute("a12345") is Some(m) else { fail("no match") }
inspect(m.content(), content="1234") // greedy: takes max
// alternation with |
let either = @string.Regex::string("cat") | @string.Regex::string("dog")
inspect(either.execute("I have a dog") is Some(_), content="true")
}pub(open) trait FromStr {
#as_free_fn
fn from_str(StringView) -> Self raise
}pub struct Regex {
// private fields
}test {
let regex = @string.Regex::string("cat") | @string.Regex::string("dog")
inspect(regex.execute("dog") is Some(_), content="true")
inspect(regex.execute("cow") is Some(_), content="false")
}test {
let regex =
guard regex.execute("a12b") is Some(m) else { fail("Expected match") }
inspect(m.content(), content="12")
}test {
let digit = .capture("number")
let regex = @string.Regex::string("ID: ") + digit
guard regex.execute("ID: 12345") is Some(m) else { fail("Expected match") }
debug_inspect(
m.named_group("number"),
content=(
#|Some(<StringView: "12345">)
),
)
}test {
let user = .capture("user")
let domain = .capture("domain")
let tld = .capture("tld")
let email = user +
@string.Regex::string("@") +
domain +
@string.Regex::string(".") +
tld
guard email.execute("john@example.com") is Some(m) else {
fail("Expected match")
}
debug_inspect(
m.named_group("user"),
content=(
#|Some(<StringView: "john">)
),
)
debug_inspect(
m.named_group("domain"),
content=(
#|Some(<StringView: "example">)
),
)
debug_inspect(
m.named_group("tld"),
content=(
#|Some(<StringView: "com">)
),
)
}test {
let regex =
let input = "a12b34"
guard regex.execute(input) is Some(first) else {
fail("Expected first match")
}
inspect(first.content(), content="12")
let next = first.before().length() + first.content().length()
guard regex.execute(input, last_index=next) is Some(second) else {
fail("Expected second match")
}
inspect(second.content(), content="34")
}test {
let anchored =
inspect(anchored.execute("ab", last_index=0) is Some(_), content="true")
inspect(anchored.execute("xaby", last_index=1) is Some(_), content="false")
}test {
let regex =
let matches = regex.find("a12b3").to_array()
inspect(matches.length(), content="2")
inspect(matches[0].content(), content="12")
inspect(matches[1].content(), content="3")
}test {
let greedy = .repeat(min=2, max=4)
guard greedy.execute("a12345") is Some(m1) else { fail("Expected match") }
inspect(m1.content(), content="1234")
let nongreedy = .repeat(min=2, max=4, greedy=false)
guard nongreedy.execute("a12345") is Some(m2) else { fail("Expected match") }
inspect(m2.content(), content="12")
}fn Regex::replace_by(regex : Regex, str : StringView, replacer : (MatchResult) -> StringView, limit? : Int) -> StringViewtest {
let regex =
let result = regex.replace_by("a12b3", m => "[\{m.content()}]")
inspect(result, content="a[12]b[3]")
}test {
let regex =
let parts = regex.split("a,,b").to_array()
inspect(parts.length(), content="3")
inspect(parts[0], content="a")
inspect(parts[1], content="")
inspect(parts[2], content="b")
}test {
let regex = @string.Regex::string("a+b(c)")
inspect(regex.execute("a+b(c)") is Some(_), content="true")
inspect(regex.execute("abcc") is Some(_), content="false")
}test {
let regex = @string.Regex::unsafe_from_string("[[:digit:]]+")
guard regex.execute("a12b") is Some(m) else { fail("Expected match") }
inspect(m.content(), content="12")
}test {
inspect(@string.parse_bigint("12345"), content="12345")
inspect(@string.parse_bigint("-ff", base=16), content="-255")
}fn parse_bool(str : StringView) -> Bool raisefn parse_double(str : StringView) -> Double raisetest {
inspect(@strconv.parse_double("123"), content="123")
inspect(@strconv.parse_double("12.34"), content="12.34")
inspect(@strconv.parse_double(".123"), content="0.123")
inspect(@strconv.parse_double("1e5"), content="100000")
inspect(@strconv.parse_double("1.2e-3"), content="0.0012")
inspect(@strconv.parse_double("1_234.5"), content="1234.5")
}fn parse_int(str : StringView, base? : Int) -> Int raisefn parse_int64(str : StringView, base? : Int) -> Int64 raisetest {
inspect(@strconv.parse_int64("123"), content="123")
inspect(@strconv.parse_int64("0xff", base=0), content="255")
inspect(@strconv.parse_int64("0o10"), content="8")
inspect(@strconv.parse_int64("0b1010"), content="10")
inspect(@strconv.parse_int64("1_234"), content="1234")
inspect(@strconv.parse_int64("-123"), content="-123")
inspect(@strconv.parse_int64("ff", base=16), content="255")
inspect(@strconv.parse_int64("zz", base=36), content="1295")
}fn parse_uint(str : StringView, base? : Int) -> UInt raisefn parse_uint64(str : StringView, base? : Int) -> UInt64 raisetest {
inspect(@strconv.parse_uint64("123"), content="123")
inspect(@strconv.parse_uint64("0xff", base=0), content="255")
inspect(@strconv.parse_uint64("0o10"), content="8")
inspect(@strconv.parse_uint64("0b1010"), content="10")
inspect(@strconv.parse_uint64("1_234"), content="1234")
inspect(@strconv.parse_uint64("ff", base=16), content="255")
inspect(@strconv.parse_uint64("zz", base=36), content="1295")
}Install
Installed by default