use crate::{
arena::{Arena, Flags, Kind, ValueId},
bignum,
};
#[cfg(feature = "alloc")]
use alloc::string::String;
#[derive(Clone, Copy)]
pub struct ValueRef<'r, 'a> {
arena: &'r Arena<'a>,
id: ValueId,
}
impl<'r, 'a> ValueRef<'r, 'a> {
#[must_use]
pub const fn new(arena: &'r Arena<'a>, id: ValueId) -> Self {
Self { arena, id }
}
#[must_use]
pub const fn root(arena: &'r Arena<'a>) -> Self {
Self {
arena,
id: arena.root(),
}
}
#[inline]
#[must_use]
pub const fn id(&self) -> ValueId {
self.id
}
#[inline]
#[must_use]
pub const fn arena(&self) -> &'r Arena<'a> {
self.arena
}
#[inline]
#[must_use]
pub fn kind(&self) -> Kind {
self.arena.node(self.id).kind
}
#[inline]
#[must_use]
pub fn is_nil(&self) -> bool {
self.kind() == Kind::Nil
}
#[must_use]
pub fn class_name(&self) -> Option<&'r [u8]> {
self.arena.class_of(self.id).map(|sym| self.arena.symbol_bytes(sym))
}
#[must_use]
pub fn is_user_class(&self) -> bool {
self.arena.node(self.id).flags.contains(Flags::USER_CLASS)
}
pub fn extensions(&self) -> impl Iterator<Item = &'r [u8]> + 'r {
let arena = self.arena;
arena.extensions_of(self.id).map(move |sym| arena.symbol_bytes(sym))
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self.kind() {
Kind::True => Some(true),
Kind::False => Some(false),
_ => None,
}
}
#[allow(clippy::cast_possible_wrap)]
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
let node = self.arena.node(self.id);
match node.kind {
Kind::Fixnum => Some(i64::from(node.a as i32)),
Kind::Bignum => {
let bytes = self.arena.blob(node.a);
if bytes.len() > 8 {
return None;
}
let mut buf = [0u8; 8];
buf[..bytes.len()].copy_from_slice(bytes);
let magnitude = u64::from_le_bytes(buf);
if node.flags.contains(Flags::NEGATIVE) {
#[allow(clippy::cast_possible_wrap)]
return (magnitude <= 1u64 << 63).then(|| (magnitude as i64).wrapping_neg());
}
i64::try_from(magnitude).ok()
}
_ => None,
}
}
#[must_use]
pub fn as_bignum_bytes(&self) -> Option<(bool, &'r [u8])> {
let node = self.arena.node(self.id);
(node.kind == Kind::Bignum).then(|| (node.flags.contains(Flags::NEGATIVE), self.arena.blob(node.a)))
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn as_bigint_decimal(&self) -> Option<String> {
let (negative, magnitude) = self.as_bignum_bytes()?;
Some(bignum::le_bytes_to_decimal(negative, magnitude))
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
let node = self.arena.node(self.id);
(node.kind == Kind::Float)
.then(|| core::str::from_utf8(self.arena.blob(node.a)).ok())
.flatten()
.and_then(|s| s.parse().ok())
}
#[must_use]
pub fn as_float_bytes(&self) -> Option<&'r [u8]> {
let node = self.arena.node(self.id);
(node.kind == Kind::Float).then(|| self.arena.blob(node.a))
}
#[must_use]
pub fn as_bytes(&self) -> Option<&'r [u8]> {
let node = self.arena.node(self.id);
matches!(node.kind, Kind::Str | Kind::Bytes).then(|| self.arena.blob(node.a))
}
#[must_use]
pub fn as_str(&self) -> Option<&'r str> {
let node = self.arena.node(self.id);
match node.kind {
Kind::Str => core::str::from_utf8(self.arena.blob(node.a)).ok(),
Kind::Symbol => self.arena.symbol_str(node.a),
_ => None,
}
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn encoding_id(&self) -> Option<u8> {
let node = self.arena.node(self.id);
match node.kind {
Kind::Bytes => Some(crate::encoding::ENCODING_ASCII_8BIT),
Kind::Str => Some(node.b as u8),
Kind::Regexp => Some((node.b >> 8) as u8),
_ => None,
}
}
#[must_use]
pub fn encoding_name(&self) -> Option<&'r [u8]> {
match self.encoding_id()? {
crate::encoding::ENCODING_CUSTOM => self.arena.custom_encoding_of(self.id),
id => crate::encoding::encoding_name(id),
}
}
#[must_use]
pub fn as_symbol_bytes(&self) -> Option<&'r [u8]> {
let node = self.arena.node(self.id);
(node.kind == Kind::Symbol).then(|| self.arena.symbol_bytes(node.a))
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn as_regexp(&self) -> Option<(&'r [u8], u8)> {
let node = self.arena.node(self.id);
(node.kind == Kind::Regexp).then(|| (self.arena.blob(node.a), node.b as u8))
}
#[must_use]
pub fn as_path(&self) -> Option<&'r [u8]> {
let node = self.arena.node(self.id);
matches!(node.kind, Kind::Class | Kind::Module).then(|| self.arena.blob(node.a))
}
#[must_use]
pub fn is_old_module(&self) -> bool {
self.arena.node(self.id).flags.contains(Flags::OLD_MODULE)
}
#[must_use]
pub fn len(&self) -> usize {
let node = self.arena.node(self.id);
match node.kind {
Kind::Array | Kind::Hash | Kind::Object | Kind::Struct => node.b as usize,
_ => 0,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn array(&self) -> impl ExactSizeIterator<Item = ValueRef<'r, 'a>> + 'r {
let arena = self.arena;
let node = arena.node(self.id);
let (start, len) = if node.kind == Kind::Array {
(node.a, node.b)
} else {
(0, 0)
};
(0..len).map(move |i| ValueRef::new(arena, arena.children[(start + i) as usize]))
}
#[must_use]
pub fn at(&self, index: usize) -> Option<Self> {
let node = self.arena.node(self.id);
(node.kind == Kind::Array && index < node.b as usize)
.then(|| ValueRef::new(self.arena, self.arena.children[node.a as usize + index]))
}
#[must_use]
pub fn entries(&self) -> impl ExactSizeIterator<Item = (ValueRef<'r, 'a>, ValueRef<'r, 'a>)> + 'r {
let arena = self.arena;
let node = arena.node(self.id);
let (start, len) = if node.kind == Kind::Hash {
(node.a, node.b)
} else {
(0, 0)
};
(0..len).map(move |i| {
let k = arena.children[(start + i * 2) as usize];
let v = arena.children[(start + i * 2 + 1) as usize];
(ValueRef::new(arena, k), ValueRef::new(arena, v))
})
}
#[must_use]
pub fn default_value(&self) -> Option<Self> {
let node = self.arena.node(self.id);
(node.kind == Kind::Hash && node.flags.contains(Flags::HAS_DEFAULT)).then(|| {
let idx = node.a as usize + node.b as usize * 2;
ValueRef::new(self.arena, self.arena.children[idx])
})
}
#[must_use]
pub fn lookup(&self, key: ValueRef<'_, '_>) -> Option<Self> {
self.entries().find(|(k, _)| value_eq(*k, key)).map(|(_, v)| v)
}
#[must_use]
pub fn lookup_symbol(&self, name: &str) -> Option<Self> {
let wanted = name.as_bytes();
self.entries()
.find(|(k, _)| k.as_symbol_bytes() == Some(wanted))
.map(|(_, v)| v)
}
#[must_use]
pub fn members(&self) -> impl ExactSizeIterator<Item = (&'r [u8], ValueRef<'r, 'a>)> + 'r {
let arena = self.arena;
let node = arena.node(self.id);
let (start, len) = if matches!(node.kind, Kind::Object | Kind::Struct) {
(node.a, node.b)
} else {
(0, 0)
};
(0..len).map(move |i| {
let (sym, v) = arena.members[(start + i) as usize];
(arena.symbol_bytes(sym), ValueRef::new(arena, v))
})
}
#[must_use]
pub fn get(&self, name: &str) -> Option<Self> {
let wanted = name.as_bytes();
self.members()
.find(|(n, _)| *n == wanted || n.strip_prefix(b"@") == Some(wanted))
.map(|(_, v)| v)
}
#[must_use]
pub fn is_data(&self) -> bool {
self.arena.node(self.id).flags.contains(Flags::DATA)
}
#[must_use]
pub fn is_user_marshal(&self) -> bool {
self.arena.node(self.id).flags.contains(Flags::USER_MARSHAL)
}
#[must_use]
pub fn is_user_defined(&self) -> bool {
self.arena.node(self.id).flags.contains(Flags::USER_DEFINED)
}
}
fn value_eq(a: ValueRef<'_, '_>, b: ValueRef<'_, '_>) -> bool {
match (a.kind(), b.kind()) {
(Kind::Nil, Kind::Nil) | (Kind::True, Kind::True) | (Kind::False, Kind::False) => true,
(Kind::Fixnum, Kind::Fixnum) => a.as_i64() == b.as_i64(),
(Kind::Bignum, Kind::Bignum) => a.as_bignum_bytes() == b.as_bignum_bytes(),
(Kind::Float, Kind::Float) => a.as_float_bytes() == b.as_float_bytes(),
(Kind::Str, Kind::Str) | (Kind::Bytes, Kind::Bytes) => a.as_bytes() == b.as_bytes(),
(Kind::Symbol, Kind::Symbol) => a.as_symbol_bytes() == b.as_symbol_bytes(),
(Kind::Array, Kind::Array) => a.len() == b.len() && a.array().zip(b.array()).all(|(x, y)| value_eq(x, y)),
_ => false,
}
}