diff --git a/builtin/builtin.mbti b/builtin/builtin.mbti index 6a6c3b521..f02d3c54f 100644 --- a/builtin/builtin.mbti +++ b/builtin/builtin.mbti @@ -412,6 +412,7 @@ impl Int { #deprecated shr(Int, Int) -> Int to_byte(Int) -> Byte + to_char(Int) -> Char? to_double(Int) -> Double to_float(Int) -> Float to_int16(Int) -> Int16 @@ -421,6 +422,7 @@ impl Int { to_uint(Int) -> UInt to_uint16(Int) -> UInt16 to_uint64(Int) -> UInt64 + unsafe_to_char(Int) -> Char until(Int, Int, step~ : Int = .., inclusive~ : Bool = ..) -> Iter[Int] #deprecated upto(Int, Int, inclusive~ : Bool = ..) -> Iter[Int] diff --git a/builtin/intrinsics.mbt b/builtin/intrinsics.mbt index 2e9a67aa3..f92912913 100644 --- a/builtin/intrinsics.mbt +++ b/builtin/intrinsics.mbt @@ -1471,6 +1471,18 @@ pub fn Bytes::new(len : Int) -> Bytes { /// ``` pub fn Int::to_byte(self : Int) -> Byte = "%i32_to_byte" +///| +pub fn Int::unsafe_to_char(self : Int) -> Char = "%char_from_int" + +///| +pub fn Int::to_char(self : Int) -> Char? { + if self is (0..=0xD7FF) || self is (0xE000..=0x10FFFF) { + Some(self.unsafe_to_char()) + } else { + None + } +} + ///| /// Converts an unsigned 64-bit integer to a byte by truncating it to fit within /// the byte range (0 to 255). diff --git a/builtin/intrinsics_test.mbt b/builtin/intrinsics_test.mbt index 280b1ed60..a363fc33f 100644 --- a/builtin/intrinsics_test.mbt +++ b/builtin/intrinsics_test.mbt @@ -465,3 +465,50 @@ test "convert byte to int64" { let result = b.to_int64() inspect!(result, content="255") } + +///| +test "Int::to_char" { + inspect!(Int::to_char(97), content="Some('a')") + inspect!(Int::to_char(65), content="Some('A')") + inspect!(Int::to_char(49), content="Some('1')") + inspect!(Int::to_char(33), content="Some('!')") +} + +///| +test "Int::to_char - Invalid values" { + inspect!(Int::to_char(-1), content="None") + inspect!(Int::to_char(1114112), content="None") + inspect!(Int::to_char(1114113), content="None") +} + +///| +test "Int::to_char - Valid values" { + inspect!(Int::to_char(0), content="Some('\\x00')") + inspect!(Int::to_char(127), content="Some('')") + inspect!(Int::to_char(128), content="Some('€')") + inspect!(Int::to_char(0x7F), content="Some('')") + inspect!(Int::to_char(0x80), content="Some('€')") + inspect!(Int::to_char(0xFF), content="Some('ÿ')") + inspect!(Int::to_char(0x100), content="Some('Ā')") + inspect!(Int::to_char(0xFFFF), content="Some('￿')") + inspect!(Int::to_char(0x10000), content="Some('𐀀')") + inspect!(Int::to_char(0x10FFFF), content="Some('􏿿')") +} + +///| +test "Int::to_char - Invalid values" { + inspect!(Int::to_char(-1), content="None") + inspect!(Int::to_char(0x10FFFF + 1), content="None") + inspect!(Int::to_char(0xD800), content="None") + inspect!(Int::to_char(0xDFFF), content="None") + inspect!(Int::to_char(0xD900), content="None") + inspect!(Int::to_char(0xDF00), content="None") +} + +///| +test "Int::to_char - Boundary cases" { + inspect!(Int::to_char(0xD7FF), content="Some('퟿')") + inspect!(Int::to_char(0xE000), content="Some('')") + inspect!(Int::to_char(0x10FFFF), content="Some('􏿿')") + inspect!(Int::to_char(0x10FFFF + 1), content="None") +} diff --git a/builtin/iter_test.mbt b/builtin/iter_test.mbt index 49ccb20ea..f55a312da 100644 --- a/builtin/iter_test.mbt +++ b/builtin/iter_test.mbt @@ -231,7 +231,7 @@ test "map" { let iter = test_from_array(['1', '2', '3', '4', '5']) let exb = StringBuilder::new(size_hint=0) iter - .map(fn { x => Char::from_int(x.to_int() + 1) }) + .map(fn { x => (x.to_int() + 1).to_char().unwrap() }) .each(fn { x => exb.write_char(x) }) inspect!(exb, content="23456") }