use std::{
cmp::Ordering,
fmt::{self, Debug, Display},
hash::Hasher,
};
use gazebo::{any::AnyLifetime, cast};
use num_bigint::BigInt;
use num_traits::Signed;
use serde::{Serialize, Serializer};
use crate::{
collections::{StarlarkHashValue, StarlarkHasher},
values::{
basic::StarlarkValueBasic, error::ValueError, float::StarlarkFloat, num::Num,
types::bigint::StarlarkBigInt, AllocFrozenValue, AllocValue, FrozenHeap, FrozenValue, Heap,
StarlarkValue, UnpackValue, Value,
},
};
pub const INT_TYPE: &str = "int";
impl<'v> AllocValue<'v> for i32 {
fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
Value::new_int(self)
}
}
impl AllocFrozenValue for i32 {
fn alloc_frozen_value(self, _heap: &FrozenHeap) -> FrozenValue {
FrozenValue::new_int(self)
}
}
impl UnpackValue<'_> for i32 {
fn expected() -> String {
"int".to_owned()
}
fn unpack_value(value: Value) -> Option<Self> {
value.unpack_int()
}
}
#[derive(AnyLifetime)]
#[repr(C)]
pub(crate) struct PointerI32 {
_private: (),
}
impl Debug for PointerI32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.get(), f)
}
}
impl PointerI32 {
pub(crate) fn new(x: i32) -> &'static Self {
unsafe { cast::usize_to_ptr(FrozenValue::new_int(x).ptr_value()) }
}
pub(crate) fn get(&self) -> i32 {
unsafe { FrozenValue::new_ptr_value(cast::ptr_to_usize(self)).unpack_int_unchecked() }
}
fn to_bigint(&self) -> BigInt {
BigInt::from(self.get())
}
pub(crate) fn type_is_pointer_i32<'v, T: StarlarkValue<'v>>() -> bool {
T::static_type_id() == PointerI32::static_type_id()
}
}
impl Display for PointerI32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl<'v> StarlarkValue<'v> for PointerI32 {
starlark_type!(INT_TYPE);
fn is_special() -> bool
where
Self: Sized,
{
true
}
fn equals(&self, other: Value) -> anyhow::Result<bool> {
Ok(match other.unpack_num() {
Some(Num::Int(other)) => self.get() == other,
Some(Num::Float(other)) => self.get() as f64 == other,
Some(Num::BigInt(b)) => *b == self.get(),
None => false,
})
}
fn to_int(&self) -> anyhow::Result<i32> {
Ok(self.get())
}
fn to_bool(&self) -> bool {
self.get() != 0
}
fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
hasher.write_u64(Num::from(self.get()).get_hash_64());
Ok(())
}
fn plus(&self, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
Ok(Value::new_int(self.get()))
}
fn minus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
Ok(self.get().checked_neg().map_or_else(
|| StarlarkBigInt::alloc_bigint(-BigInt::from(self.get()), heap),
Value::new_int,
))
}
fn add(&self, other: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
match other.unpack_num() {
Some(Num::Int(other)) => Some(Ok(self.get().checked_add(other).map_or_else(
|| StarlarkBigInt::alloc_bigint(self.to_bigint() + other, heap),
Value::new_int,
))),
Some(Num::Float(_)) => StarlarkFloat(self.get() as f64).add(other, heap),
Some(Num::BigInt(other)) => Some(Ok(StarlarkBigInt::alloc_bigint(
self.get() + other.get(),
heap,
))),
None => None,
}
}
fn sub(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
Some(Num::Int(other)) => Ok(self.get().checked_sub(other).map_or_else(
|| StarlarkBigInt::alloc_bigint(self.to_bigint() - other, heap),
Value::new_int,
)),
Some(Num::Float(_)) => StarlarkFloat(self.get() as f64).sub(other, heap),
Some(Num::BigInt(other)) => {
Ok(StarlarkBigInt::alloc_bigint(self.get() - other.get(), heap))
}
None => ValueError::unsupported_with(self, "-", other),
}
}
fn mul(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
if let Some(other) = other.unpack_int() {
Ok(self.get().checked_mul(other).map_or_else(
|| StarlarkBigInt::alloc_bigint(self.to_bigint() * other, heap),
Value::new_int,
))
} else {
other.mul(Value::new_int(self.get()), heap)
}
}
fn div(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
if other.unpack_num().is_some() {
StarlarkFloat(self.get() as f64).div(other, heap)
} else {
ValueError::unsupported_with(self, "/", other)
}
}
fn percent(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None => ValueError::unsupported_with(self, "%", other),
Some(Num::Float(_)) => return StarlarkFloat(self.get() as f64).percent(other, heap),
Some(Num::BigInt(other)) => {
return StarlarkBigInt::percent_big(&BigInt::from(self.get()), other.get(), heap);
}
Some(Num::Int(b)) => {
let a = self.get();
if b == 0 {
return Err(ValueError::DivisionByZero.into());
}
if self.get() == i32::min_value() && b == -1 {
return Ok(Value::new_int(0));
}
let r = a % b;
if r == 0 {
Ok(Value::new_int(0))
} else {
Ok(Value::new_int(if b.signum() != r.signum() {
r + b
} else {
r
}))
}
}
}
}
fn floor_div(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
let rhs = match other.unpack_num() {
None => return ValueError::unsupported_with(self, "//", other),
Some(rhs) => rhs,
};
match rhs {
Num::Float(_) => StarlarkFloat(self.get() as f64).floor_div(other, heap),
Num::Int(b) => {
let a = self.get();
if b == 0 {
return Err(ValueError::DivisionByZero.into());
}
let sig = b.signum() * a.signum();
let offset = if sig < 0 && a % b != 0 { 1 } else { 0 };
match a.checked_div(b) {
Some(div) => Ok(Value::new_int(div - offset)),
None => StarlarkBigInt::floor_div_big(&BigInt::from(a), &BigInt::from(b), heap),
}
}
Num::BigInt(b) => {
StarlarkBigInt::floor_div_big(&BigInt::from(self.get()), b.get(), heap)
}
}
}
fn compare(&self, other: Value) -> anyhow::Result<Ordering> {
match other.unpack_num() {
Some(Num::Int(other)) => Ok(self.get().cmp(&other)),
Some(Num::Float(_)) => StarlarkFloat(self.get() as f64).compare(other),
Some(Num::BigInt(b)) => Ok(StarlarkBigInt::cmp_small_big(self.get(), b)),
None => ValueError::unsupported_with(self, "==", other),
}
}
fn bit_and(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None | Some(Num::Float(_)) => ValueError::unsupported_with(self, "&", other),
Some(Num::Int(i)) => Ok(Value::new_int(self.get() & i)),
Some(Num::BigInt(b)) => Ok(StarlarkBigInt::alloc_bigint(
&self.to_bigint() & b.get(),
heap,
)),
}
}
fn bit_or(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None | Some(Num::Float(_)) => ValueError::unsupported_with(self, "|", other),
Some(Num::Int(i)) => Ok(Value::new_int(self.get() | i)),
Some(Num::BigInt(b)) => Ok(StarlarkBigInt::alloc_bigint(
&self.to_bigint() | b.get(),
heap,
)),
}
}
fn bit_xor(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None | Some(Num::Float(_)) => ValueError::unsupported_with(self, "^", other),
Some(Num::Int(i)) => Ok(Value::new_int(self.get() ^ i)),
Some(Num::BigInt(b)) => Ok(StarlarkBigInt::alloc_bigint(
&self.to_bigint() ^ b.get(),
heap,
)),
}
}
fn bit_not(&self, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
Ok(Value::new_int(!self.get()))
}
fn left_shift(&self, other: Value, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None | Some(Num::Float(_)) => ValueError::unsupported_with(self, "<<", other),
Some(Num::Int(other)) => {
if let Ok(other) = other.try_into() {
if let Some(r) = self.get().checked_shl(other) {
Ok(Value::new_int(r))
} else {
Err(ValueError::IntegerOverflow.into())
}
} else {
Err(ValueError::NegativeShiftCount.into())
}
}
Some(Num::BigInt(b)) => {
if b.get().is_negative() {
Err(ValueError::NegativeShiftCount.into())
} else if self.get() == 0 {
Ok(Value::new_int(0))
} else {
Err(ValueError::IntegerOverflow.into())
}
}
}
}
#[allow(clippy::collapsible_else_if)]
fn right_shift(&self, other: Value, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
match other.unpack_num() {
None | Some(Num::Float(_)) => ValueError::unsupported_with(self, ">>", other),
Some(Num::Int(other)) => {
if let Ok(other) = other.try_into() {
if let Some(r) = self.get().checked_shr(other) {
Ok(Value::new_int(r))
} else {
Err(ValueError::IntegerOverflow.into())
}
} else {
Err(ValueError::NegativeShiftCount.into())
}
}
Some(Num::BigInt(b)) => {
if b.get().is_negative() {
Err(ValueError::NegativeShiftCount.into())
} else {
if self.get() >= 0 {
Ok(Value::new_int(0))
} else {
Ok(Value::new_int(-1))
}
}
}
}
}
}
impl<'v> StarlarkValueBasic<'v> for PointerI32 {
fn get_hash(&self) -> StarlarkHashValue {
Num::from(self.get()).get_hash()
}
}
impl Serialize for PointerI32 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_i32(self.get())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assert;
#[test]
fn test_arithmetic_operators() {
assert::all_true(
r#"
+1 == 1
-1 == 0 - 1
1 + 2 == 3
1 + 2.0 == 3.0
1 - 2 == -1
1 - 2.0 == -1.0
2 * 3 == 6
2 * 3.0 == 6.0
4 / 2 == 2.0
5 % 3 == 2
4 // 2 == 2
"#,
);
}
#[test]
fn test_minus() {
assert::eq("2147483648", "-(-2147483647 - 1)")
}
#[test]
fn test_int_tag() {
fn check(x: i32) {
assert_eq!(x, PointerI32::new(x).get())
}
for x in -10..10 {
check(x)
}
check(i32::MAX);
check(i32::MIN);
}
#[test]
fn test_alignment_int_pointer() {
assert_eq!(1, std::mem::align_of::<PointerI32>());
}
}