diff --git a/src/core/inttypes.c b/src/core/inttypes.c index 5de40112..cc7a5787 100644 --- a/src/core/inttypes.c +++ b/src/core/inttypes.c @@ -238,6 +238,61 @@ JANET_CORE_FN(cfun_to_number, janet_panicf("expected int/u64 or int/s64, got %q", argv[0]); } +JANET_CORE_FN(cfun_to_bytes, + "(int/to-bytes value &opt endianness buffer)", + "Write the bytes of an `int/s64` or `int/u64` into a buffer.\n" + "The `buffer` parameter specifies an existing buffer to write to, if unset a new buffer will be created.\n" + "Returns the modified buffer.\n" + "The `endianness` paramater indicates the byte order:\n" + "- `nil` (unset): system byte order\n" + "- `:le`: little-endian, least significant byte first\n" + "- `:be`: big-endian, most significant byte first\n") { + janet_arity(argc, 1, 3); + if (janet_is_int(argv[0]) == JANET_INT_NONE) { + janet_panicf("int/to-bytes: expected an int/s64 or int/u64, got %q", argv[0]); + } + + int reverse = 0; + if (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) { + JanetKeyword endianness_kw = janet_getkeyword(argv, 1); + if (!janet_cstrcmp(endianness_kw, "le")) { +#if JANET_BIG_ENDIAN + reverse = 1; +#endif + } else if (!janet_cstrcmp(endianness_kw, "be")) { +#if JANET_LITTLE_ENDIAN + reverse = 1; +#endif + } else { + janet_panicf("int/to-bytes: expected endianness :le, :be or nil, got %v", argv[1]); + } + } + + JanetBuffer *buffer = NULL; + if (argc > 2 && !janet_checktype(argv[2], JANET_NIL)) { + if (!janet_checktype(argv[2], JANET_BUFFER)) { + janet_panicf("int/to-bytes: expected buffer or nil, got %q", argv[2]); + } + + buffer = janet_unwrap_buffer(argv[2]); + janet_buffer_extra(buffer, 8); + } else { + buffer = janet_buffer(8); + } + + uint8_t *bytes = janet_unwrap_abstract(argv[0]); + if (reverse) { + for (int i = 0; i < 8; ++i) { + buffer->data[buffer->count + 7 - i] = bytes[i]; + } + } else { + memcpy(buffer->data + buffer->count, bytes, 8); + } + buffer->count += 8; + + return janet_wrap_buffer(buffer); +} + /* * Code to support polymorphic comparison. * int/u64 and int/s64 support a "compare" method that allows @@ -546,6 +601,7 @@ void janet_lib_inttypes(JanetTable *env) { JANET_CORE_REG("int/s64", cfun_it_s64_new), JANET_CORE_REG("int/u64", cfun_it_u64_new), JANET_CORE_REG("int/to-number", cfun_to_number), + JANET_CORE_REG("int/to-bytes", cfun_to_bytes), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, it_cfuns); diff --git a/test/suite0006.janet b/test/suite0006.janet index 3c4cd551..34633ebd 100644 --- a/test/suite0006.janet +++ b/test/suite0006.janet @@ -91,6 +91,39 @@ "trap INT64_MIN / -1" (:/ (int/s64 "-0x8000_0000_0000_0000") -1)) +# int/s64 and int/u64 serialization +(assert (deep= (int/to-bytes (u64 0)) @"\x00\x00\x00\x00\x00\x00\x00\x00")) + +(assert (deep= (int/to-bytes (i64 1) :le) @"\x01\x00\x00\x00\x00\x00\x00\x00")) +(assert (deep= (int/to-bytes (i64 1) :be) @"\x00\x00\x00\x00\x00\x00\x00\x01")) +(assert (deep= (int/to-bytes (i64 -1)) @"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF")) +(assert (deep= (int/to-bytes (i64 -5) :be) @"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFB")) + +(assert (deep= (int/to-bytes (u64 1) :le) @"\x01\x00\x00\x00\x00\x00\x00\x00")) +(assert (deep= (int/to-bytes (u64 1) :be) @"\x00\x00\x00\x00\x00\x00\x00\x01")) +(assert (deep= (int/to-bytes (u64 300) :be) @"\x00\x00\x00\x00\x00\x00\x01\x2C")) + +# int/s64 int/u64 to existing buffer +(let [buf1 @"" + buf2 @"abcd"] + (assert (deep= (int/to-bytes (i64 1) :le buf1) @"\x01\x00\x00\x00\x00\x00\x00\x00")) + (assert (deep= buf1 @"\x01\x00\x00\x00\x00\x00\x00\x00")) + (assert (deep= (int/to-bytes (u64 300) :be buf2) @"abcd\x00\x00\x00\x00\x00\x00\x01\x2C"))) + +# int/s64 and int/u64 paramater type checking +(assert-error + "bad value passed to int/to-bytes" + (int/to-bytes 1)) + +(assert-error + "invalid endianness passed to int/to-bytes" + (int/to-bytes (u64 0) :little)) + +(assert-error + "invalid buffer passed to int/to-bytes" + (int/to-bytes (u64 0) :little :buffer)) + + # Dynamic bindings (setdyn :a 10) (assert (= 40 (with-dyns [:a 25 :b 15] (+ (dyn :a) (dyn :b)))) "dyn usage 1")