From 34a90df4a3e002ffd1e69cbae93b9a2e13805c8e Mon Sep 17 00:00:00 2001 From: "P. Oscar Boykin" Date: Tue, 10 Dec 2024 16:49:11 -1000 Subject: [PATCH] Add string_to_Int to predef (#1307) --- c_runtime/bosatsu_ext_Bosatsu_l_Predef.c | 4 ++ c_runtime/bosatsu_ext_Bosatsu_l_Predef.h | 2 + c_runtime/bosatsu_runtime.c | 44 +++++++++++++++++++ c_runtime/bosatsu_runtime.h | 2 + .../src/main/resources/bosatsu/predef.bosatsu | 2 + .../main/scala/org/bykn/bosatsu/Predef.scala | 13 ++++++ .../bykn/bosatsu/codegen/python/Code.scala | 2 + .../bosatsu/codegen/python/PythonGen.scala | 22 ++++++++++ test_workspace/PredefTests.bosatsu | 26 +++++++++++ 9 files changed, 117 insertions(+) diff --git a/c_runtime/bosatsu_ext_Bosatsu_l_Predef.c b/c_runtime/bosatsu_ext_Bosatsu_l_Predef.c index db13e3b4c..88e3aef92 100644 --- a/c_runtime/bosatsu_ext_Bosatsu_l_Predef.c +++ b/c_runtime/bosatsu_ext_Bosatsu_l_Predef.c @@ -201,6 +201,10 @@ BValue ___bsts_g_Bosatsu_l_Predef_l_string__Order__fn(BValue a, BValue b) { return alloc_enum0(result + 1); } +BValue ___bsts_g_Bosatsu_l_Predef_l_string__to__Int(BValue a) { + return bsts_string_to_integer(a); +} + BValue ___bsts_g_Bosatsu_l_Predef_l_sub(BValue a, BValue b) { return ___bsts_g_Bosatsu_l_Predef_l_add(a, bsts_integer_negate(b)); } diff --git a/c_runtime/bosatsu_ext_Bosatsu_l_Predef.h b/c_runtime/bosatsu_ext_Bosatsu_l_Predef.h index a02ba2456..82bc1a315 100644 --- a/c_runtime/bosatsu_ext_Bosatsu_l_Predef.h +++ b/c_runtime/bosatsu_ext_Bosatsu_l_Predef.h @@ -36,6 +36,8 @@ BValue ___bsts_g_Bosatsu_l_Predef_l_shift__right__Int(BValue a, BValue b); BValue ___bsts_g_Bosatsu_l_Predef_l_string__Order__fn(BValue a, BValue b); +BValue ___bsts_g_Bosatsu_l_Predef_l_string__to__Int(BValue a); + BValue ___bsts_g_Bosatsu_l_Predef_l_sub(BValue a, BValue b); BValue ___bsts_g_Bosatsu_l_Predef_l_times(BValue a, BValue b); diff --git a/c_runtime/bosatsu_runtime.c b/c_runtime/bosatsu_runtime.c index 9c148ec45..66dca4e7c 100644 --- a/c_runtime/bosatsu_runtime.c +++ b/c_runtime/bosatsu_runtime.c @@ -1105,6 +1105,50 @@ BValue bsts_integer_to_string(BValue v) { } } +// String -> Option[Integer] +BValue bsts_string_to_integer(BValue v) { + size_t slen = bsts_string_utf8_len(v); + char* bytes = bsts_string_utf8_bytes(v); + if (slen == 0) return alloc_enum0(0); + + size_t pos = 0; + _Bool sign = 0; + if (bytes[pos] == '-') { + sign = 1; + pos++; + if (slen == 1) return alloc_enum0(0); + } + // at least 1 character + + int64_t acc = 0; + BValue bacc = 0; + while(pos < slen) { + int32_t digit = (int32_t)(bytes[pos] - '0'); + if ((digit < 0) || (9 < digit)) return alloc_enum0(0); + if (pos >= 10) { + if (pos == 10) { + // we could be overflowing an int32_t at this point + bacc = bsts_integer_from_int64(((int64_t)acc) * 10L); + } + else { + bacc = bsts_integer_times(bacc, bsts_integer_from_int(10)); + } + bacc = bsts_integer_add(bacc, bsts_integer_from_int(digit)); + } + else { + acc = acc * 10 + digit; + } + pos++; + } + if (slen < 11) { + // acc should hold the number + return alloc_enum1(1, bsts_integer_from_int64(sign ? -acc : acc)); + } + else { + return alloc_enum1(1, sign ? bsts_integer_negate(bacc) : bacc); + } +} + // Function to convert sign-magnitude to two's complement representation void sign_magnitude_to_twos_complement(_Bool sign, size_t len, uint32_t* words, uint32_t* result_words, size_t result_len) { memcpy(result_words, words, len * sizeof(uint32_t)); diff --git a/c_runtime/bosatsu_runtime.h b/c_runtime/bosatsu_runtime.h index 07d5e5add..4277346ca 100644 --- a/c_runtime/bosatsu_runtime.h +++ b/c_runtime/bosatsu_runtime.h @@ -108,6 +108,8 @@ int bsts_integer_cmp(BValue l, BValue r); BValue bsts_integer_negate(BValue v); // &Integer -> String BValue bsts_integer_to_string(BValue v); +// String -> Option[Integer] +BValue bsts_string_to_integer(BValue v); // (&Integer, &Integer) -> (Integer, Integer) // div_mod(l, r) == (d, m) <=> l = r * d + m BValue bsts_integer_div_mod(BValue l, BValue r); diff --git a/core/src/main/resources/bosatsu/predef.bosatsu b/core/src/main/resources/bosatsu/predef.bosatsu index db0e67cd9..af0b5cef9 100644 --- a/core/src/main/resources/bosatsu/predef.bosatsu +++ b/core/src/main/resources/bosatsu/predef.bosatsu @@ -62,6 +62,7 @@ export ( get_key, int_loop, int_to_String, + string_to_Int, items, map_List, mod_Int, @@ -249,6 +250,7 @@ external def partition_String(arg: String, sep: String) -> Option[(String, Strin external def rpartition_String(arg: String, sep: String) -> Option[(String, String)] external def int_to_String(i: Int) -> String +external def string_to_Int(s: String) -> Option[Int] external def trace(prefix: String, item: a) -> a diff --git a/core/src/main/scala/org/bykn/bosatsu/Predef.scala b/core/src/main/scala/org/bykn/bosatsu/Predef.scala index 87c8b2962..b609150b5 100644 --- a/core/src/main/scala/org/bykn/bosatsu/Predef.scala +++ b/core/src/main/scala/org/bykn/bosatsu/Predef.scala @@ -51,6 +51,11 @@ object Predef { "int_to_String", FfiCall.Fn1(PredefImpl.int_to_String(_)) ) + .add( + packageName, + "string_to_Int", + FfiCall.Fn1(PredefImpl.string_to_Int(_)) + ) .add(packageName, "trace", FfiCall.Fn2(PredefImpl.trace(_, _))) .add( packageName, @@ -226,6 +231,14 @@ object PredefImpl { final def int_to_String(intValue: Value): Value = Value.Str(i(intValue).toString) + final def string_to_Int(strValue: Value): Value = { + val Value.Str(str) = strValue + try Value.VOption.some(VInt(new BigInteger(str))) + catch { + case _: NumberFormatException => Value.VOption.none + } + } + def trace(prefix: Value, v: Value): Value = { val Value.Str(prestr) = prefix println(s"$prestr: $v") diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/python/Code.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/python/Code.scala index 3b257e4be..c24901474 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/python/Code.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/python/Code.scala @@ -969,6 +969,7 @@ object Code { case Const.Plus => (that == Const.Plus) || (that == Const.Minus) case Const.Minus => false case Const.And => that == Const.And + case Const.Or => that == Const.Or case Const.Times => // (a * b) * c == a * (b * c) // (a * b) + c != a * (b + c) @@ -1008,6 +1009,7 @@ object Code { case object BitwiseShiftLeft extends IntOp("<<") case object BitwiseShiftRight extends IntOp(">>") case object And extends Operator("and") + case object Or extends Operator("or") case object Eq extends Operator("==") case object Neq extends Operator("!=") case object Gt extends Operator(">") diff --git a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala index 6bf895857..d3b813b9c 100644 --- a/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/codegen/python/PythonGen.scala @@ -940,6 +940,28 @@ object PythonGen { 1 ) ), + ( + Identifier.unsafeBindable("string_to_Int"), + ( + { input => + Env.onLast(input.head) { s => + // int(s) if (s[0] == '-' and s[1:].isdigit()) or s.isdigit() else None + val isdigit = Code.Ident("isdigit") + val isValid = Code.Op( + (s.get(0) =:= Code.PyString("-")).evalAnd( + Code.SelectRange(s, Some(Code.Const.One), None).dot(isdigit)() + ), + Code.Const.Or, + s.dot(isdigit)()) + + Code.Ternary(Code.MakeTuple(Code.Const.One :: Code.Ident("int")(s) :: Nil), + isValid, + Code.MakeTuple(Code.Const.Zero :: Nil)) + } + }, + 1 + ) + ), ( Identifier.unsafeBindable("char_to_String"), // we encode chars as strings so this is just identity diff --git a/test_workspace/PredefTests.bosatsu b/test_workspace/PredefTests.bosatsu index 53dfe8e5e..c7191a9f3 100644 --- a/test_workspace/PredefTests.bosatsu +++ b/test_workspace/PredefTests.bosatsu @@ -5,6 +5,11 @@ operator % = mod_Int i = int_to_String +def oi(opt): + match opt: + case None: "None" + case Some(v): "Some(${i(v)})" + test_int = TestSuite("Int tests", [ Assertion((4 % -3) matches -2, "(4 % -3) == -2 got: ${i(4 % -3)}"), Assertion((-8 % -2) matches 0, "(-8 % -2) == 0 got: ${i(-8 % -2)}"), @@ -60,6 +65,27 @@ test_int = TestSuite("Int tests", [ Assertion(int_to_String(0) matches "0", "0 str"), Assertion(int_to_String(123) matches "123", "123 str"), Assertion(int_to_String(-123) matches "-123", "-123 str"), + + Assertion(string_to_Int("123") matches Some(123), "123 string_to_Int"), + Assertion(string_to_Int("-123") matches Some(-123), "-123 string_to_Int"), + Assertion(string_to_Int("-123x") matches None, "-123x string_to_Int"), + Assertion(string_to_Int("-${int_to_String(123)}") matches Some(-123), "-123 string_to_Int"), + + Assertion(string_to_Int("9223372036854775807") matches Some(9223372036854775807), + "Long.Max ${oi(string_to_Int("9223372036854775807"))} != ${oi(Some(9223372036854775807))}string_to_Int"), + Assertion(string_to_Int("9223372036854775808") matches Some(9223372036854775808), + "Long.Max + 1: ${oi(string_to_Int("9223372036854775808"))} string_to_Int"), + + Assertion(string_to_Int("2147483647") matches Some(2147483647), "Int.Max string_to_Int"), + Assertion(string_to_Int("2147483648") matches Some(2147483648), + "Int.Max + 1 string_to_Int: ${oi(string_to_Int("2147483648"))} != ${oi(Some(2147483648))}"), + + Assertion(string_to_Int("-9223372036854775808") matches Some(-9223372036854775808), "Long.Min string_to_Int"), + Assertion(string_to_Int("-9223372036854775809") matches Some(-9223372036854775809), "Long.Min - 1 string_to_Int"), + + Assertion(string_to_Int("-2147483648") matches Some(-2147483648), "Int.Min string_to_Int"), + Assertion(string_to_Int("-2147483649") matches Some(-2147483649), "Int.Min - 1 string_to_Int"), + Assertion(string_to_Int("-2147483649z") matches None, "-2147483649z string_to_Int"), ]) test_string = TestSuite("String tests", [