use crate::{LuaNumber, LuaTableEntry};
#[cfg(any(
target_arch = "aarch64",
target_arch = "x86_64",
target_arch = "wasm32",
))]
use static_assertions::assert_eq_size;
use std::{
borrow::Cow,
fmt::{Debug, Formatter},
str::{from_utf8, Utf8Error},
};
#[derive(Clone, PartialEq)]
pub enum LuaValue<'a> {
Nil,
Boolean(bool),
String(Cow<'a, [u8]>),
Number(LuaNumber),
Table(Vec<LuaTableEntry<'a>>),
}
#[cfg(any(
target_arch = "aarch64",
target_arch = "x86_64",
target_arch = "wasm32",
))]
assert_eq_size!((usize, usize, LuaNumber), LuaValue<'_>);
impl Debug for LuaValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Nil => write!(f, "Nil"),
Self::Boolean(b) => f.debug_tuple("Boolean").field(b).finish(),
Self::String(s) => f
.debug_tuple("String")
.field(&s.escape_ascii().to_string())
.finish(),
Self::Number(n) => f.debug_tuple("Number").field(n).finish(),
Self::Table(t) => f.debug_tuple("Table").field(t).finish(),
}
}
}
impl<'a> LuaValue<'a> {
#[inline]
pub const fn integer(v: i64) -> Self {
Self::Number(LuaNumber::Integer(v))
}
#[inline]
pub const fn float(v: f64) -> Self {
Self::Number(LuaNumber::Float(v))
}
#[inline]
pub const fn is_nan(&self) -> bool {
matches!(self, LuaValue::Number(n) if n.is_nan())
}
#[inline]
pub const fn is_finite(&self) -> bool {
matches!(self, LuaValue::Number(n) if n.is_finite())
}
#[inline]
pub const fn is_infinite(&self) -> bool {
matches!(self, LuaValue::Number(n) if n.is_infinite())
}
#[inline]
pub const fn is_borrowed(&self) -> bool {
matches!(self, LuaValue::String(Cow::Borrowed(_)))
}
#[inline]
pub fn as_bytes(&'a self) -> Option<Cow<'a, [u8]>> {
match self {
Self::String(s) => Some(Cow::Borrowed(s)),
_ => None,
}
}
pub fn as_str(&'a self) -> Option<Cow<'a, str>> {
match self {
Self::String(s) => from_utf8_cow(Cow::Borrowed(s)).ok(),
_ => None,
}
}
pub fn as_str_lossy(&'a self) -> Option<Cow<'a, str>> {
match self {
Self::String(s) => Some(from_utf8_cow_lossy(Cow::Borrowed(s))),
_ => None,
}
}
#[inline]
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
_ => None,
}
}
#[inline]
pub const fn as_f64(&self) -> Option<f64> {
match self {
Self::Number(n) => n.as_f64(),
_ => None,
}
}
pub const fn as_i64(&self) -> Option<i64> {
match self {
Self::Number(n) => n.as_i64(),
_ => None,
}
}
}
impl<T> From<T> for LuaValue<'_>
where
LuaNumber: From<T>,
{
fn from(value: T) -> Self {
Self::Number(LuaNumber::from(value))
}
}
impl<'a> From<&'a [u8]> for LuaValue<'a> {
fn from(value: &'a [u8]) -> Self {
Self::String(value.into())
}
}
impl<'a, const N: usize> From<&'a [u8; N]> for LuaValue<'a> {
fn from(value: &'a [u8; N]) -> Self {
Self::String(value.as_slice().into())
}
}
impl<'a> From<&'a str> for LuaValue<'a> {
fn from(value: &'a str) -> Self {
Self::String(Cow::Borrowed(value.as_bytes()))
}
}
impl From<String> for LuaValue<'_> {
fn from(value: String) -> Self {
Self::String(Cow::Owned(value.into_bytes()))
}
}
impl From<bool> for LuaValue<'_> {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl<'a> FromIterator<(&'a [u8], LuaValue<'a>)> for LuaValue<'a> {
fn from_iter<T: IntoIterator<Item = (&'a [u8], LuaValue<'a>)>>(iter: T) -> Self {
LuaValue::Table(iter.into_iter().map(From::from).collect())
}
}
impl<'a> FromIterator<(&'a str, LuaValue<'a>)> for LuaValue<'a> {
fn from_iter<T: IntoIterator<Item = (&'a str, LuaValue<'a>)>>(iter: T) -> Self {
LuaValue::Table(iter.into_iter().map(From::from).collect())
}
}
impl<'a> From<Vec<LuaTableEntry<'a>>> for LuaValue<'a> {
fn from(value: Vec<LuaTableEntry<'a>>) -> Self {
LuaValue::Table(value)
}
}
impl<'a> FromIterator<LuaTableEntry<'a>> for LuaValue<'a> {
fn from_iter<T: IntoIterator<Item = LuaTableEntry<'a>>>(iter: T) -> Self {
LuaValue::Table(iter.into_iter().collect())
}
}
macro_rules! lua_value_tryfrom_number {
($($ty:ty)*) => {$(
impl TryFrom<$ty> for LuaValue<'_> {
type Error = <LuaNumber as TryFrom<$ty>>::Error;
fn try_from(value: $ty) -> Result<Self, Self::Error> {
LuaNumber::try_from(value).map(Self::Number)
}
}
)*};
}
lua_value_tryfrom_number! { u64 isize usize i128 u128 }
impl<'a, T> From<Option<T>> for LuaValue<'a>
where
LuaValue<'a>: From<T>,
{
fn from(value: Option<T>) -> Self {
value.map(LuaValue::from).unwrap_or(LuaValue::Nil)
}
}
pub(crate) fn from_utf8_cow(v: Cow<'_, [u8]>) -> Result<Cow<'_, str>, (Utf8Error, Cow<'_, [u8]>)> {
match v {
Cow::Borrowed(v) => from_utf8(v)
.map(Cow::Borrowed)
.map_err(|e| (e, Cow::Borrowed(v))),
Cow::Owned(v) => String::from_utf8(v)
.map(Cow::Owned)
.map_err(|e| (e.utf8_error(), Cow::Owned(e.into_bytes()))),
}
}
pub(crate) fn from_utf8_cow_lossy(v: Cow<'_, [u8]>) -> Cow<'_, str> {
match v {
Cow::Borrowed(v) => String::from_utf8_lossy(v),
Cow::Owned(v) => Cow::Owned(String::from_utf8_lossy(&v).into_owned()),
}
}
pub(crate) fn to_utf8_cow(v: Cow<'_, str>) -> Cow<'_, [u8]> {
match v {
Cow::Borrowed(v) => Cow::Borrowed(v.as_bytes()),
Cow::Owned(v) => Cow::Owned(v.into_bytes()),
}
}
impl PartialEq<LuaNumber> for LuaValue<'_> {
fn eq(&self, other: &LuaNumber) -> bool {
match self {
LuaValue::Number(n) => n == other,
_ => false,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::{cmp::PartialEq, fmt::Debug};
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
wasm_bindgen_test_configure!(run_in_browser);
fn assert_cow_eq<'a, T, U>(expected: U, is_borrowed: bool, actual: Cow<'a, T>)
where
T: ToOwned + Debug + ?Sized,
<T as ToOwned>::Owned: Debug,
T: PartialEq,
U: AsRef<T> + Debug + PartialEq<Cow<'a, T>>,
{
assert_eq!(is_borrowed, matches!(actual, Cow::Borrowed(_)));
assert_eq!(expected, actual);
}
#[test]
#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
fn cow() {
assert_cow_eq("foo", true, from_utf8_cow(b"foo".into()).unwrap());
assert_cow_eq("foo", true, from_utf8_cow_lossy(b"foo".into()));
assert_cow_eq("foo", false, from_utf8_cow(b"foo".to_vec().into()).unwrap());
assert_cow_eq("foo", false, from_utf8_cow_lossy(b"foo".to_vec().into()));
let (_, cow) = from_utf8_cow(b"\xFEfoo".into()).unwrap_err();
assert_cow_eq(Cow::Borrowed(b"\xFEfoo".as_slice()), true, cow);
let cow = from_utf8_cow_lossy(b"\xFEfoo".into());
assert_cow_eq("\u{FFFD}foo", false, cow);
}
#[test]
#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
fn from_bool_option() {
assert_eq!(LuaValue::Boolean(true), LuaValue::from(true));
assert_eq!(LuaValue::Boolean(false), LuaValue::from(false));
assert_eq!(LuaValue::Nil, LuaValue::from(None::<bool>));
assert_eq!(LuaValue::Boolean(true), LuaValue::from(Some(true)));
assert_eq!(LuaValue::Boolean(false), LuaValue::from(Some(false)));
}
#[test]
#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
fn from_integer() {
for x in [0, i64::MIN, i64::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x), v);
assert_eq!(LuaValue::integer(x), LuaNumber::Integer(x));
assert_eq!(LuaNumber::Integer(x), LuaValue::integer(x));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
for x in [0, i32::MIN, i32::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
for x in [0, i16::MIN, i16::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
for x in [0, i8::MIN, i8::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
assert_eq!(LuaValue::integer(0), LuaValue::try_from(0u64).unwrap());
LuaValue::try_from(u64::MAX).unwrap_err();
for x in [0, u32::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
for x in [0, u16::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
for x in [0, u8::MAX] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::integer(x.into()), v);
assert_eq!(LuaValue::integer(x.into()), LuaNumber::Integer(x.into()));
assert_eq!(LuaNumber::Integer(x.into()), LuaValue::integer(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::integer(1), v);
}
}
#[test]
#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)]
fn from_float() {
for x in [0., f64::MIN, f64::MAX, f64::INFINITY, f64::NEG_INFINITY] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::float(x), v);
assert_eq!(LuaValue::float(x), LuaNumber::Float(x));
assert_eq!(LuaNumber::Float(x), LuaValue::float(x));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::float(1.), v);
}
let f = LuaValue::from(f64::NAN);
assert!(matches!(f, LuaValue::Number(LuaNumber::Float(x)) if x.is_nan()));
for x in [0., f32::MIN, f32::MAX, f32::INFINITY, f32::NEG_INFINITY] {
let v = LuaValue::from(x);
assert_eq!(LuaValue::float(x.into()), v);
assert_eq!(LuaValue::float(x.into()), LuaNumber::Float(x.into()));
assert_eq!(LuaNumber::Float(x.into()), LuaValue::float(x.into()));
assert_ne!(LuaValue::Boolean(false), v);
assert_ne!(LuaValue::float(1.), v);
}
let f = LuaValue::from(f32::NAN);
assert!(matches!(f, LuaValue::Number(LuaNumber::Float(x)) if x.is_nan()));
}
}