use crate::panic::{RuntimeError, runtime_error};
#[inline]
pub fn index(i: i64, len: usize) -> usize {
if i < 0 || i as u64 >= len as u64 {
runtime_error(RuntimeError::Index { index: i, len });
}
i as usize
}
#[inline]
pub fn index_u(i: u64, len: usize) -> usize {
if i >= len as u64 {
runtime_error(RuntimeError::IndexU { index: i, len });
}
i as usize
}
pub fn slice_bounds(lo: i64, hi: Option<i64>, len: usize) -> (usize, usize) {
let h = match hi {
Some(h) => {
if h < 0 || h as u64 > len as u64 {
runtime_error(RuntimeError::SliceHigh { high: h, len });
}
h as usize
}
None => len,
};
if lo < 0 || lo as u64 > h as u64 {
runtime_error(RuntimeError::SliceLow { low: lo, high: h });
}
(lo as usize, h)
}
pub mod float {
#[cfg(target_arch = "x86_64")]
#[inline]
fn cvt64(x: f64) -> i64 {
if (-9223372036854775808.0..9223372036854775808.0).contains(&x) {
x as i64
} else {
i64::MIN
}
}
#[cfg(target_arch = "x86_64")]
#[inline]
fn cvt32(x: f64) -> i32 {
if (-2147483649.0..2147483648.0).contains(&x) {
x as i32
} else {
i32::MIN
}
}
#[cfg(not(target_arch = "x86_64"))]
#[inline]
fn cvt64(x: f64) -> i64 {
x as i64
}
#[cfg(not(target_arch = "x86_64"))]
#[inline]
fn cvt32(x: f64) -> i32 {
x as i32
}
#[inline]
pub fn to_i64(x: f64) -> i64 {
cvt64(x)
}
#[inline]
pub fn to_i32(x: f64) -> i32 {
cvt32(x)
}
#[inline]
pub fn to_u64(x: f64) -> u64 {
#[cfg(target_arch = "x86_64")]
{
if x < 9223372036854775808.0 {
cvt64(x) as u64
} else {
cvt64(x - 9223372036854775808.0) as u64 | 1 << 63
}
}
#[cfg(not(target_arch = "x86_64"))]
{
x as u64
}
}
#[inline]
pub fn to_u32(x: f64) -> u32 {
cvt64(x) as u32
}
}
pub fn slice3_bounds(
lo: i64,
hi: Option<i64>,
max: Option<i64>,
len: usize,
cap: usize,
) -> (usize, usize, usize) {
let m = match max {
Some(m) => {
if m < 0 || m as u64 > cap as u64 {
runtime_error(RuntimeError::SliceCap { max: m, cap });
}
m as usize
}
None => cap,
};
let h = match hi {
Some(h) => {
if h < 0 || h as u64 > m as u64 {
runtime_error(RuntimeError::SliceHigh { high: h, len: m });
}
h as usize
}
None => len.min(m),
};
if lo < 0 || lo as u64 > h as u64 {
runtime_error(RuntimeError::SliceLow { low: lo, high: h });
}
(lo as usize, h, m)
}
pub fn make_bounds(len: i64, cap: i64) -> (usize, usize) {
if cap < 0 {
runtime_error(RuntimeError::MakeCap { cap });
}
if len < 0 || len > cap {
runtime_error(RuntimeError::MakeLen { len });
}
(len as usize, cap as usize)
}
#[inline]
pub fn shift_count(s: i64) -> u64 {
if s < 0 {
runtime_error(RuntimeError::NegativeShift);
}
s as u64
}
pub trait GoInt: Copy {
fn go_shl(self, s: u64) -> Self;
fn go_shr(self, s: u64) -> Self;
fn go_div(self, rhs: Self) -> Self;
fn go_rem(self, rhs: Self) -> Self;
}
macro_rules! go_int {
($($t:ty => $shr_saturated:expr),* $(,)?) => {$(
impl GoInt for $t {
#[inline]
fn go_shl(self, s: u64) -> Self {
if s >= u64::from(<$t>::BITS) { 0 } else { self << s }
}
#[inline]
fn go_shr(self, s: u64) -> Self {
if s >= u64::from(<$t>::BITS) {
let saturated: fn($t) -> $t = $shr_saturated;
saturated(self)
} else {
self >> s
}
}
#[inline]
fn go_div(self, rhs: Self) -> Self {
if rhs == 0 {
runtime_error(RuntimeError::DivideByZero);
}
self.wrapping_div(rhs)
}
#[inline]
fn go_rem(self, rhs: Self) -> Self {
if rhs == 0 {
runtime_error(RuntimeError::DivideByZero);
}
self.wrapping_rem(rhs)
}
}
)*};
}
go_int! {
u8 => |_| 0, u16 => |_| 0, u32 => |_| 0, u64 => |_| 0,
i8 => |x| x >> (i8::BITS - 1),
i16 => |x| x >> (i16::BITS - 1),
i32 => |x| x >> (i32::BITS - 1),
i64 => |x| x >> (i64::BITS - 1),
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::panic::GoPanic;
use std::panic::catch_unwind;
fn panic_text(f: impl FnOnce() + std::panic::UnwindSafe) -> std::string::String {
let payload = catch_unwind(f).expect_err("expected a Go panic");
let p = payload.downcast::<GoPanic>().expect("payload is a GoPanic");
std::string::String::from_utf8(p.text().to_vec()).unwrap()
}
#[test]
fn shifts_past_width() {
assert_eq!(1u8.go_shl(8), 0);
assert_eq!(1u64.go_shl(63), 1 << 63);
assert_eq!(1u64.go_shl(64), 0);
assert_eq!(1u64.go_shl(u64::MAX), 0);
assert_eq!(u32::MAX.go_shr(32), 0);
assert_eq!((-8i64).go_shr(2), -2);
assert_eq!((-8i64).go_shr(64), -1);
assert_eq!(8i64.go_shr(1000), 0);
assert_eq!((-1i8).go_shl(8), 0);
}
#[test]
fn negative_shift_count_panics() {
assert_eq!(shift_count(3), 3);
assert_eq!(
panic_text(|| {
shift_count(-1);
}),
"runtime error: negative shift amount"
);
}
#[test]
fn index_and_slice_messages_match_gc() {
assert_eq!(index(2, 3), 2);
assert_eq!(slice_bounds(1, None, 5), (1, 5));
let cases: [(&str, fn()); 7] = [
("runtime error: index out of range [-1]", || {
index(-1, 5);
}),
(
"runtime error: index out of range [10] with length 5",
|| {
index(10, 5);
},
),
(
"runtime error: index out of range [9223372036854775808] with length 5",
|| {
index_u(1 << 63, 5);
},
),
(
"runtime error: slice bounds out of range [:10] with length 5",
|| {
slice_bounds(0, Some(10), 5);
},
),
("runtime error: slice bounds out of range [3:2]", || {
slice_bounds(3, Some(2), 5);
}),
("runtime error: slice bounds out of range [10:5]", || {
slice_bounds(10, None, 5);
}),
("runtime error: slice bounds out of range [:-1]", || {
slice_bounds(0, Some(-1), 5);
}),
];
for (want, f) in cases {
assert_eq!(panic_text(f), want);
}
assert_eq!(
panic_text(|| {
slice_bounds(-1, None, 5);
}),
"runtime error: slice bounds out of range [-1:]"
);
}
#[test]
fn division() {
assert_eq!((-7i64).go_div(2), -3);
assert_eq!((-7i64).go_rem(2), -1);
assert_eq!(i64::MIN.go_div(-1), i64::MIN);
assert_eq!(i64::MIN.go_rem(-1), 0);
assert_eq!(i8::MIN.go_div(-1), i8::MIN);
assert_eq!(
panic_text(|| {
1u32.go_div(0);
}),
"runtime error: integer divide by zero"
);
assert_eq!(
panic_text(|| {
1i16.go_rem(0);
}),
"runtime error: integer divide by zero"
);
}
}