diff --git a/bigint/bigint.mbt b/bigint/bigint.mbt index 8cf88da4..4019600b 100644 --- a/bigint/bigint.mbt +++ b/bigint/bigint.mbt @@ -659,9 +659,9 @@ pub fn to_hex(self : BigInt) -> String { let y = x % 16 x /= 16 tmp = if y < 10 { - Char::from_int(y + 48).to_string() + Char::from_int(y + 48).unwrap().to_string() } else { - Char::from_int(y + 55).to_string() + Char::from_int(y + 55).unwrap().to_string() } + tmp } if i != self.len - 1 && tmp.length() < 4 { diff --git a/bigint/moon.pkg.json b/bigint/moon.pkg.json index 6ae45bb3..4ea1fba4 100644 --- a/bigint/moon.pkg.json +++ b/bigint/moon.pkg.json @@ -2,6 +2,7 @@ "import": [ "moonbitlang/core/builtin", "moonbitlang/core/char", + "moonbitlang/core/option", "moonbitlang/core/coverage" ], "test_import": ["moonbitlang/core/assertion"] diff --git a/builtin/builtin.mbti b/builtin/builtin.mbti index ac24d4b1..32763116 100644 --- a/builtin/builtin.mbti +++ b/builtin/builtin.mbti @@ -82,7 +82,7 @@ impl Byte { impl Char { compare(Char, Char) -> Int default() -> Char - from_int(Int) -> Char + from_int_unsafe(Int) -> Char op_equal(Char, Char) -> Bool to_int(Char) -> Int } diff --git a/builtin/console.mbt b/builtin/console.mbt index e018e1d9..5a347f5a 100644 --- a/builtin/console.mbt +++ b/builtin/console.mbt @@ -50,7 +50,7 @@ pub fn to_string(self : Int64) -> String { if num2 != 0L { write_digits(num2) } - buf.write_char(Char::from_int(abs(num % 10L).to_int() + 48)) + buf.write_char(Char::from_int_unsafe(abs(num % 10L).to_int() + 48)) } write_digits(self) @@ -78,7 +78,7 @@ pub fn to_string(self : Int) -> String { if num2 != 0 { write_digits(num2) } - buf.write_char(Char::from_int(abs(num % 10) + 48)) + buf.write_char(Char::from_int_unsafe(abs(num % 10) + 48)) } write_digits(self) diff --git a/builtin/debug.mbt b/builtin/debug.mbt index 1ea9a2bb..85ff7dcc 100644 --- a/builtin/debug.mbt +++ b/builtin/debug.mbt @@ -99,9 +99,9 @@ pub fn debug_write(self : String, buf : Buffer) -> Unit { fn to_hex_digit(i : Int) -> Char { if i < 10 { - Char::from_int('0'.to_int() + i) + Char::from_int_unsafe('0'.to_int() + i) } else { - Char::from_int('a'.to_int() + (i - 10)) + Char::from_int_unsafe('a'.to_int() + (i - 10)) } } diff --git a/builtin/intrinsics.mbt b/builtin/intrinsics.mbt index d17d9578..d415b374 100644 --- a/builtin/intrinsics.mbt +++ b/builtin/intrinsics.mbt @@ -184,7 +184,8 @@ pub fn Double::convert_i64_u(val : Int64) -> Double = "%i64_to_f64_u" pub fn Char::to_int(self : Char) -> Int = "%char_to_int" -pub fn Char::from_int(val : Int) -> Char = "%char_from_int" +// Converts a Int to a Char, ignoring validity. +pub fn Char::from_int_unsafe(val : Int) -> Char = "%char_from_int" pub fn Char::op_equal(self : Char, other : Char) -> Bool = "%char_eq" diff --git a/char/char.mbt b/char/char.mbt index 66cfcfbb..fbb9dddc 100644 --- a/char/char.mbt +++ b/char/char.mbt @@ -28,9 +28,9 @@ test "to_string" { pub fn debug_write(self : Char, buf : Buffer) -> Unit { fn to_hex_digit(i : Int) -> Char { if i < 10 { - Char::from_int('0'.to_int() + i) + Char::from_int('0'.to_int() + i).unwrap() } else { - Char::from_int('a'.to_int() + (i - 10)) + Char::from_int('a'.to_int() + (i - 10)).unwrap() } } @@ -61,7 +61,7 @@ pub fn debug_write(self : Char, buf : Buffer) -> Unit { test "debug_write" { fn repr(chr) { - let buf = Buffer::make(0) + let buf = Buffer::make(4) debug_write(chr, buf) buf.to_string() } @@ -74,9 +74,49 @@ test "debug_write" { @assertion.assert_eq(repr('\r'), "'\\r'")? @assertion.assert_eq(repr('\b'), "'\\b'")? @assertion.assert_eq(repr('\t'), "'\\t'")? - @assertion.assert_eq(repr(Char::from_int(0)), "'\\x00'")? + @assertion.assert_eq(repr('\x0f'), "'\\x0f'")? + @assertion.assert_eq(repr(Char::from_int_unsafe(0)), "'\\x00'")? } pub fn hash(self : Char) -> Int { self.to_int() } + +// The lowest valid code point a char can have, '\u{0}'. +pub let min : Char = '\u{0}' + +// The highest valid code point a char can have, '\u{10FFFF}'. +pub let max : Char = '\u{10FFFF}' + +let lo_bound : Char = '\u{D7FF}' + +let hi_bound : Char = '\u{E000}' + +pub fn is_valid(self : Char) -> Bool { + min <= self && self <= lo_bound || hi_bound <= self && self <= max +} + +// Converts a Int to a Char. +pub fn Char::from_int(i : Int) -> Option[Char] { + let c = Char::from_int_unsafe(i) + if c.is_valid() { + Some(c) + } else { + None + } +} + +test "invalid" { + inspect(Char::from_int_unsafe(0xD800).is_valid(), content="false")? + inspect(Char::from_int(0xD800), content="None")? + inspect(Char::from_int(-1), content="None")? +} + +test "Unicode" { + inspect(Char::from_int_unsafe(97), content="a")? + inspect('中'.to_int(), content="20013")? + inspect(Char::from_int(20013), content="Some(中)")? + inspect(Char::from_int_unsafe(20013), content="中")? + inspect('🤣'.to_int(), content="129315")? + inspect(Char::from_int(129315), content="Some(🤣)")? +} diff --git a/char/char.mbti b/char/char.mbti index 28cfb248..304fe429 100644 --- a/char/char.mbti +++ b/char/char.mbti @@ -1,11 +1,16 @@ package moonbitlang/core/char // Values +let max : Char + +let min : Char // Types and methods impl Char { debug_write(Char, Buffer) -> Unit + from_int(Int) -> Option[Char] hash(Char) -> Int + is_valid(Char) -> Bool to_string(Char) -> String } diff --git a/char/moon.pkg.json b/char/moon.pkg.json index 144575e9..92e5bacd 100644 --- a/char/moon.pkg.json +++ b/char/moon.pkg.json @@ -2,6 +2,7 @@ "import": [ "moonbitlang/core/builtin", "moonbitlang/core/assertion", + "moonbitlang/core/option", "moonbitlang/core/coverage" ] } diff --git a/coverage/coverage.mbt b/coverage/coverage.mbt index b5679e7c..4cf62b06 100644 --- a/coverage/coverage.mbt +++ b/coverage/coverage.mbt @@ -102,9 +102,9 @@ fn escape_to(s : String, buf : Buffer) -> Unit { fn to_hex_digit(i : Int) -> Char { if i < 10 { - Char::from_int('0'.to_int() + i) + Char::from_int_unsafe('0'.to_int() + i) } else { - Char::from_int('a'.to_int() + (i - 10)) + Char::from_int_unsafe('a'.to_int() + (i - 10)) } } diff --git a/double/moon.pkg.json b/double/moon.pkg.json index 7eacc139..ac6799db 100644 --- a/double/moon.pkg.json +++ b/double/moon.pkg.json @@ -1,7 +1,8 @@ { "import": [ "moonbitlang/core/builtin", - + "moonbitlang/core/char", + "moonbitlang/core/option", "moonbitlang/core/assertion", "moonbitlang/core/bool", "moonbitlang/core/int64", diff --git a/double/ryu.mbt b/double/ryu.mbt index e390cb80..650e3e65 100644 --- a/double/ryu.mbt +++ b/double/ryu.mbt @@ -29,7 +29,7 @@ fn log10Pow2(e : Int) -> Int { fn string_from_bytes(bytes : Bytes, from : Int, to : Int) -> String { let buf = Buffer::make(bytes.length()) for i = from; i < to; i = i + 1 { - buf.write_char(Char::from_int(bytes[i])) + buf.write_char(Char::from_int(bytes[i]).unwrap()) } buf.to_string() } diff --git a/iter/iter_test.mbt b/iter/iter_test.mbt index 5ea59f79..57b049bf 100644 --- a/iter/iter_test.mbt +++ b/iter/iter_test.mbt @@ -150,7 +150,7 @@ test "filter" { test "map" { let iter = from_array(['1', '2', '3', '4', '5']) let exb = Buffer::make(0) - iter.map(fn { x => Char::from_int(x.to_int() + 1) }).iter( + iter.map(fn { x => Char::from_int(x.to_int() + 1).unwrap() }).iter( fn { x => exb.write_char(x) }, ) exb.expect(content="23456")? @@ -176,7 +176,7 @@ test "flat_map2" { test "fold" { let iter = from_array(['1', '2', '3', '4', '5']) - let result = Char::from_int( + let result = Char::from_int_unsafe( iter.fold(fn { acc, x => acc + x.to_int() }, 0) / 5, ) @assertion.assert_eq(result, '3')? diff --git a/iter/moon.pkg.json b/iter/moon.pkg.json index 67cfc54a..fae7b78d 100644 --- a/iter/moon.pkg.json +++ b/iter/moon.pkg.json @@ -5,6 +5,7 @@ "moonbitlang/core/coverage" ], "test_import": [ - "moonbitlang/core/char" + "moonbitlang/core/char", + "moonbitlang/core/option" ] } diff --git a/json5/lex_misc.mbt b/json5/lex_misc.mbt index adc2de03..930fe4ae 100644 --- a/json5/lex_misc.mbt +++ b/json5/lex_misc.mbt @@ -23,7 +23,7 @@ fn read_char(ctx : ParseContext) -> Option[Char] { if c2 >= 0xDC00 && c2 <= 0xDFFF { ctx.offset += 1 let c3 = c1.lsl(10) + c2 - 0x35fdc00 - return Some(Char::from_int(c3)) + return Some(Char::from_int_unsafe(c3)) } } } diff --git a/json5/lex_prop.mbt b/json5/lex_prop.mbt index 4e401feb..b1b04c4d 100644 --- a/json5/lex_prop.mbt +++ b/json5/lex_prop.mbt @@ -27,7 +27,7 @@ fn lex_property_name(ctx : ParseContext) -> Result[Token, ParseError] { Some('\\') => { lex_assert_char(ctx, 'u')? let c = lex_hex_digits(ctx, 4)? - let c = Char::from_int(c) + let c = Char::from_int_unsafe(c) if c == '$' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c > '\x7f' && non_ascii_id_start.contains(c) { let buffer = StringBuilder::make() @@ -69,7 +69,7 @@ fn lex_ident( flush(ctx.offset - 1) lex_assert_char(ctx, 'u')? let c = lex_hex_digits(ctx, 4)? - let c = Char::from_int(c) + let c = Char::from_int_unsafe(c) if c == '$' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c > '\x7f' && non_ascii_id_continue.contains(c) { buffer.add_char(c) diff --git a/json5/lex_string.mbt b/json5/lex_string.mbt index a5cc61c9..fe5e71ac 100644 --- a/json5/lex_string.mbt +++ b/json5/lex_string.mbt @@ -55,11 +55,11 @@ fn lex_string(ctx : ParseContext, quote : Char) -> Result[String, ParseError] { } Some('x') => { let c = lex_hex_digits(ctx, 2)? - buf.add_char(Char::from_int(c)) + buf.add_char(Char::from_int_unsafe(c)) } Some('u') => { let c = lex_hex_digits(ctx, 4)? - buf.add_char(Char::from_int(c)) + buf.add_char(Char::from_int_unsafe(c)) } Some(c) => { if c >= '1' && c <= '9' { diff --git a/strconv/moon.pkg.json b/strconv/moon.pkg.json index e7b1b727..1084a09c 100644 --- a/strconv/moon.pkg.json +++ b/strconv/moon.pkg.json @@ -7,6 +7,7 @@ "moonbitlang/core/tuple", "moonbitlang/core/char", "moonbitlang/core/string", - "moonbitlang/core/int64" + "moonbitlang/core/int64", + "moonbitlang/core/option" ] } diff --git a/strconv/string_slice.mbt b/strconv/string_slice.mbt index bc59d3a3..6b5581bf 100644 --- a/strconv/string_slice.mbt +++ b/strconv/string_slice.mbt @@ -102,7 +102,7 @@ fn prefix_eq_ignore_case(self : StringSlice, s2 : String) -> Bool { fn lower(c : Char) -> Char { if 'A' <= c && c <= 'Z' { - Char::from_int(c.to_int() + 'a'.to_int() - 'A'.to_int()) + Char::from_int(c.to_int() + 'a'.to_int() - 'A'.to_int()).unwrap() } else { c } diff --git a/string/moon.pkg.json b/string/moon.pkg.json index a160d39c..2b6601aa 100644 --- a/string/moon.pkg.json +++ b/string/moon.pkg.json @@ -5,6 +5,7 @@ "moonbitlang/core/coverage", "moonbitlang/core/bytes", "moonbitlang/core/char", - "moonbitlang/core/iter" + "moonbitlang/core/iter", + "moonbitlang/core/option" ] } diff --git a/string/string.mbt b/string/string.mbt index febf6918..4a987e10 100644 --- a/string/string.mbt +++ b/string/string.mbt @@ -159,7 +159,7 @@ pub fn as_iter(self : String) -> @iter.Iter[Char] { if c >= 0xD800 && c <= 0xDBFF && index + 1 < len { let c2 = self[index + 1].to_int() if c2 >= 0xDC00 && c2 <= 0xDFFF { - let c = Char::from_int((c - 0xD800) * 0x400 + c2 - 0xDC00 + 0x10000) + let c = Char::from_int((c - 0xD800) * 0x400 + c2 - 0xDC00 + 0x10000).unwrap() if yield(c) { continue index + 2 } else { @@ -168,7 +168,7 @@ pub fn as_iter(self : String) -> @iter.Iter[Char] { } } //TODO: handle garbage input - if not(yield(Char::from_int(c))) { + if not(yield(Char::from_int(c).unwrap())) { break false } } else {