// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn add_prefix_zero(s : String, len : Int) -> String {
let buf = StringBuilder(size_hint=len)
for i = s.length(); i < len; i = i + 1 {
buf.write_char('0')
}
buf.write_string(s)
buf.to_string()
}
///|
test "add_prefix_zero" {
inspect(add_prefix_zero("", 2), content="00")
inspect(add_prefix_zero("1", 0), content="1")
inspect(add_prefix_zero("1", 1), content="1")
inspect(add_prefix_zero("1", 2), content="01")
inspect(add_prefix_zero("1", 3), content="001")
}
///|
fn remove_suffix_zero(str : StringView) -> StringView {
for s = str {
match s {
[_] as x => break x // keep one zero if all are zeros
[.. head, '0'] => continue head
x => break x
}
}
}
///|
///
/// Formats a nanosecond value as a fractional seconds string for ISO 8601 output.
///
/// Pads the nanosecond value to 9 digits, trims trailing zeros, and prepends a dot.
/// For example:
/// format_nanos(0L) => ".0"
/// format_nanos(1000100L) => ".0010001"
/// format_nanos(100_000_000L) => ".1"
/// format_nanos(110_000_000L) => ".11"
///
/// This is used for formatting fractional seconds in time and duration string outputs.
fn format_nanos(nanos : Int64) -> String {
let nanos_str = nanos.to_string().pad_start(9, '0')
StringView::add(".", remove_suffix_zero(nanos_str)).to_owned()
}
///|
test "remove_suffix_zero && format_nanos" {
inspect(remove_suffix_zero(""), content="")
inspect(remove_suffix_zero("1000100"), content="10001")
inspect(remove_suffix_zero("000"), content="0")
inspect(format_nanos(0L), content=".0")
inspect(format_nanos(1000100L), content=".0010001")
inspect(format_nanos(100_000_000L), content=".1")
inspect(format_nanos(110_000_000L), content=".11")
}
///|
fn add_suffix_zero(s : String, len : Int) -> String {
let buf = StringBuilder(size_hint=len)
buf.write_string(s)
for i = s.length(); i < len; i = i + 1 {
buf.write_char('0')
}
buf.to_string()
}
///|
test "add_suffix_zero" {
inspect(add_suffix_zero("1", 0), content="1")
inspect(add_suffix_zero("1", 1), content="1")
inspect(add_suffix_zero("1", 2), content="10")
inspect(add_suffix_zero("1", 3), content="100")
}
///|
/// FIXME: use split method of String
fn split(s : String, delimiter : Char) -> Array[String] {
let spl = []
if s.length() == 0 {
return spl
}
let buf = StringBuilder(size_hint=0)
let delimiter_code = Char::to_int(delimiter).to_uint16()
for i = 0; i < s.length(); i = i + 1 {
let code_unit = s.code_unit_at(i)
if code_unit == delimiter_code {
spl.push(buf.to_string())
buf.reset()
} else {
buf.write_char(UInt16::unsafe_to_char(code_unit))
}
}
spl.push(buf.to_string())
spl
}
///|
test "split" {
debug_inspect(
split("2000-01-01", '-'),
content=(
#|["2000", "01", "01"]
),
)
debug_inspect(
split("-01-01", '-'),
content=(
#|["", "01", "01"]
),
)
debug_inspect(
split("12-31", '-'),
content=(
#|["12", "31"]
),
)
debug_inspect(
split("-01", '-'),
content=(
#|["", "01"]
),
)
debug_inspect(
split("-", '-'),
content=(
#|["", ""]
),
)
debug_inspect(split("", '-'), content="[]")
debug_inspect(
split("0", '-'),
content=(
#|["0"]
),
)
}
///|
fn[T] int_overflow_err() -> T raise Error {
raise Failure::Failure("Int overflow")
}
///|
fn[T] int64_overflow_err() -> T raise Error {
raise Failure::Failure("Int64 overflow")
}
///|
/// FIXME: use checked_add method of Int
fn checked_add_int(x : Int, y : Int) -> Int raise Error {
let r = x.to_int64() + y.to_int64()
if r < @int.MIN_VALUE.to_int64() || r > @int.MAX_VALUE.to_int64() {
int_overflow_err()
} else {
r.to_int()
}
}
///|
/// FIXME: use checked_mul method of Int
fn checked_mul_int(x : Int, y : Int) -> Int raise Error {
let r = x.to_int64() * y.to_int64()
if r < @int.MIN_VALUE.to_int64() || r > @int.MAX_VALUE.to_int64() {
int_overflow_err()
} else {
r.to_int()
}
}
///|
/// FIXME: use checked_add method of Int64
fn checked_add_int64(x : Int64, y : Int64) -> Int64 raise Error {
let r = x + y
// Overflow iff both arguments have the opposite sign of the result
if ((x ^ r) & (y ^ r)) < 0L {
int64_overflow_err()
} else {
r
}
}
///|
/// FIXME: use checked_mul method of Int64
fn checked_mul_int64(x : Int64, y : Int64) -> Int64 raise Error {
let r = x * y
let abs_x = x.abs()
let abs_y = y.abs()
if ((abs_x ^ abs_y).reinterpret_as_uint64() >> 31).reinterpret_as_int64() !=
0L {
// bits that greater than 2^31 might cause overflow
if (y != 0L && r / y != x) || (x == @int64.MIN_VALUE && y == -1L) {
int64_overflow_err()
}
}
r
}
///|
test "checked_add_int" {
inspect(checked_add_int(1, 1), content="2")
inspect(checked_add_int(1, -1), content="0")
inspect(checked_add_int(-1, -1), content="-2")
debug_inspect(
@test.expect_error(() => checked_add_int(@int.MAX_VALUE, 1)),
content=(
#|Failure("Int overflow")
),
)
debug_inspect(
@test.expect_error(() => checked_add_int(@int.MIN_VALUE, -1)),
content=(
#|Failure("Int overflow")
),
)
}
///|
test "checked_mul_int" {
inspect(checked_mul_int(2, 3), content="6")
inspect(checked_mul_int(2, -3), content="-6")
inspect(checked_mul_int(-2, -3), content="6")
debug_inspect(
@test.expect_error(() => checked_mul_int(@int.MAX_VALUE, 2)),
content=(
#|Failure("Int overflow")
),
)
debug_inspect(
@test.expect_error(() => checked_mul_int(@int.MIN_VALUE, 2)),
content=(
#|Failure("Int overflow")
),
)
}
///|
test "checked_add_int64" {
inspect(checked_add_int64(1L, 1L), content="2")
inspect(checked_add_int64(1L, -1L), content="0")
inspect(checked_add_int64(-1L, -1L), content="-2")
debug_inspect(
@test.expect_error(() => checked_add_int64(1L, @int64.MAX_VALUE)),
content=(
#|Failure("Int64 overflow")
),
)
debug_inspect(
@test.expect_error(() => checked_add_int64(-1L, @int64.MIN_VALUE)),
content=(
#|Failure("Int64 overflow")
),
)
}
///|
test "checked_mul_int64" {
inspect(checked_mul_int64(2L, 3L), content="6")
inspect(checked_mul_int64(2L, -3L), content="-6")
inspect(checked_mul_int64(-2L, -3L), content="6")
debug_inspect(
@test.expect_error(() => checked_mul_int64(@int64.MAX_VALUE, 2L)),
content=(
#|Failure("Int64 overflow")
),
)
debug_inspect(
@test.expect_error(() => checked_mul_int64(@int64.MIN_VALUE, 2L)),
content=(
#|Failure("Int64 overflow")
),
)
}
///|
fn floor(x : Double) -> Double {
if x.is_pos_inf() || x.is_neg_inf() || x.is_nan() {
return x
}
let n = x.to_int64()
let d = n.to_double()
if x >= 0.0 || x == d {
d
} else {
d - 1.0
}
}
///|
fn floor_div_int(x : Int, y : Int) -> Int raise Error {
if y == 0 {
raise Failure::Failure("division by zero")
}
let r = x.to_double() / y.to_double()
floor(r).to_int()
}
///|
test "floor_div_int" {
inspect(floor_div_int(1, 10), content="0")
inspect(floor_div_int(-1, 10), content="-1")
inspect(floor_div_int(9, 10), content="0")
inspect(floor_div_int(-9, 10), content="-1")
inspect(floor_div_int(0, 10), content="0")
debug_inspect(
@test.expect_error(() => floor_div_int(10, 0)),
content=(
#|Failure("division by zero")
),
)
}
///|
fn floor_div_int64(x : Int64, y : Int64) -> Int64 raise Error {
if y == 0L {
fail("division by zero")
}
let r = x.to_double() / y.to_double()
floor(r).to_int64()
}
///|
test "floor_div_int64" {
inspect(floor_div_int64(1L, 10L), content="0")
inspect(floor_div_int64(-1L, 10L), content="-1")
inspect(floor_div_int64(9L, 10L), content="0")
inspect(floor_div_int64(-9L, 10L), content="-1")
inspect(floor_div_int64(0L, 10L), content="0")
@test.assert_raise(() => floor_div_int64(10L, 0L))
}
///|
fn floor_mod_int(x : Int, y : Int) -> Int raise Error {
x - floor_div_int(x, y) * y
}
///|
test "floor_mod_int" {
inspect(floor_mod_int(1, 10), content="1")
inspect(floor_mod_int(-1, 10), content="9")
inspect(floor_mod_int(11, 10), content="1")
inspect(floor_mod_int(-11, 10), content="9")
inspect(floor_mod_int(0, 10), content="0")
debug_inspect(
@test.expect_error(() => floor_mod_int(10, 0)),
content=(
#|Failure("division by zero")
),
)
}
///|
fn floor_mod_int64(x : Int64, y : Int64) -> Int64 raise Error {
x - floor_div_int64(x, y) * y
}
///|
test "floor_mod_int64" {
inspect(floor_mod_int64(1L, 10L), content="1")
inspect(floor_mod_int64(-1L, 10L), content="9")
inspect(floor_mod_int64(11L, 10L), content="1")
inspect(floor_mod_int64(-11L, 10L), content="9")
inspect(floor_mod_int64(0L, 10L), content="0")
@test.assert_raise(() => floor_mod_int64(10L, 0L))
}