use hermes_ast::context::GCLock;
use hermes_ast::node::{BinaryExpression, Node, NumericLiteral, UnaryExpression};
use hermes_ast::node_child::NodeMetadata;
use hermes_support::location::SMRange;
use crate::keywords::Keywords;
fn truncate_to_i32(d: f64) -> i32 {
let bits = d.to_bits();
let exp_field = ((bits >> 52) & 0x7FF) as i32;
if exp_field == 0 {
return 0;
}
let sign: i64 = 1 - (((bits as i64) >> 62) & 2);
let mut m: u64 = bits & 0x000F_FFFF_FFFF_FFFF;
let exp = exp_field - (1023 + 52);
m |= 1u64 << 52;
if exp >= 0 {
if exp <= 31 {
let shifted = (m << (exp as u32)) as i64;
sign.wrapping_mul(shifted) as i32
} else {
0
}
} else {
if exp > -53 {
let shifted = (m >> ((-exp) as u32)) as i64;
sign.wrapping_mul(shifted) as i32
} else {
0
}
}
}
fn truncate_to_u32(d: f64) -> u32 {
truncate_to_i32(d) as u32
}
fn folded_metadata<'gc>(
range: SMRange,
operand_metadata: &NodeMetadata<'gc>,
) -> NodeMetadata<'gc> {
let md =
NodeMetadata::new_with_debug(range, operand_metadata.debug_loc.get());
md.parens.set(operand_metadata.parens.get());
md
}
pub fn ast_fold_binary_expression<'gc>(
gc: &'gc GCLock<'_, '_>,
kw: &Keywords,
be: &BinaryExpression<'gc>,
) -> Option<&'gc Node<'gc>> {
let Node::NumericLiteral(left_num) = be.left else {
return None;
};
let Node::NumericLiteral(right_num) = be.right else {
return None;
};
let left = left_num.value.get();
let right = right_num.value.get();
let op = be.operator.get();
let res: f64 = if op == kw.ident_plus {
left + right
} else if op == kw.ident_minus {
left - right
} else if op == kw.ident_star {
left * right
} else if op == kw.ident_slash {
left / right
} else if op == kw.ident_percent {
left % right
} else if op == kw.ident_amp {
(truncate_to_i32(left) & truncate_to_i32(right)) as f64
} else if op == kw.ident_caret {
(truncate_to_i32(left) ^ truncate_to_i32(right)) as f64
} else if op == kw.ident_pipe {
(truncate_to_i32(left) | truncate_to_i32(right)) as f64
} else if op == kw.ident_less_less {
let shifted =
truncate_to_u32(left).wrapping_shl(truncate_to_u32(right) & 0x1f);
(shifted as i32) as f64
} else if op == kw.ident_greater_greater {
(truncate_to_i32(left) >> (truncate_to_u32(right) & 0x1f)) as f64
} else if op == kw.ident_greater_greater_greater {
(truncate_to_u32(left) >> (truncate_to_u32(right) & 0x1f)) as f64
} else {
return None;
};
let md = folded_metadata(be.metadata.range.get(), &left_num.metadata);
Some(gc.alloc(Node::NumericLiteral(NumericLiteral::new(md, res))))
}
pub fn ast_fold_unary_expression<'gc>(
gc: &'gc GCLock<'_, '_>,
kw: &Keywords,
ue: &UnaryExpression<'gc>,
) -> Option<&'gc Node<'gc>> {
let Node::NumericLiteral(num) = ue.argument else {
return None;
};
let val = num.value.get();
let op = ue.operator.get();
let res: f64 = if op == kw.ident_plus {
val
} else if op == kw.ident_minus {
-val
} else if op == kw.ident_tilde {
(!truncate_to_i32(val)) as f64
} else {
return None;
};
let md = folded_metadata(ue.metadata.range.get(), &num.metadata);
Some(gc.alloc(Node::NumericLiteral(NumericLiteral::new(md, res))))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_to_i32_known_values() {
assert_eq!(truncate_to_i32(0.0), 0);
assert_eq!(truncate_to_i32(-0.0), 0);
assert_eq!(truncate_to_i32(f64::NAN), 0);
assert_eq!(truncate_to_i32(f64::INFINITY), 0);
assert_eq!(truncate_to_i32(f64::NEG_INFINITY), 0);
assert_eq!(truncate_to_i32(3.7), 3);
assert_eq!(truncate_to_i32(-3.7), -3);
assert_eq!(truncate_to_i32(4294967297.0), 1); assert_eq!(truncate_to_i32(-1.0), -1);
assert_eq!(truncate_to_i32(2147483648.0), i32::MIN); assert_eq!(truncate_to_i32(4294967295.0), -1); assert_eq!(truncate_to_i32(1e300), 0);
}
#[test]
fn truncate_to_u32_known_values() {
assert_eq!(truncate_to_u32(-1.0), u32::MAX);
assert_eq!(truncate_to_u32(4294967295.0), u32::MAX);
assert_eq!(truncate_to_u32(0.0), 0);
assert_eq!(truncate_to_u32(f64::NAN), 0);
}
}