use crate::nodes::{Expression, NumberExpression, StringExpression};
#[derive(Debug, Clone, Default, PartialEq)]
pub enum LuaValue {
False,
Function,
Nil,
Number(f64),
String(Vec<u8>),
Table,
True,
#[default]
Unknown,
}
impl LuaValue {
pub fn is_truthy(&self) -> Option<bool> {
match self {
Self::Unknown => None,
Self::Nil | Self::False => Some(false),
_ => Some(true),
}
}
pub fn map_if_truthy<F>(self, map: F) -> Self
where
F: Fn(Self) -> Self,
{
match self.is_truthy() {
Some(true) => map(self),
Some(false) => self,
_ => Self::Unknown,
}
}
pub fn map_if_truthy_else<F, G>(self, map: F, default: G) -> Self
where
F: Fn(Self) -> Self,
G: Fn() -> Self,
{
match self.is_truthy() {
Some(true) => map(self),
Some(false) => default(),
_ => Self::Unknown,
}
}
pub fn to_expression(self) -> Option<Expression> {
match self {
Self::False => Some(Expression::from(false)),
Self::True => Some(Expression::from(true)),
Self::Nil => Some(Expression::nil()),
Self::String(value) => Some(StringExpression::from_value(value).into()),
Self::Number(value) => Some(Expression::from(value)),
_ => None,
}
}
pub fn number_coercion(self) -> Self {
match &self {
Self::String(string) => str::from_utf8(string)
.ok()
.map(str::trim)
.and_then(|string| {
let number = if string.starts_with('-') {
string
.get(1..)
.and_then(|string| string.parse::<NumberExpression>().ok())
.map(|number| -number.compute_value())
} else {
string
.parse::<NumberExpression>()
.ok()
.map(|number| number.compute_value())
};
number.map(LuaValue::Number)
}),
_ => None,
}
.unwrap_or(self)
}
pub fn string_coercion(self) -> Self {
match &self {
Self::Number(value) => Some(Self::from(value.to_string())),
_ => None,
}
.unwrap_or(self)
}
pub fn length(&self) -> LuaValue {
match self {
Self::String(value) => LuaValue::Number(value.len() as f64),
_ => LuaValue::Unknown,
}
}
}
impl From<bool> for LuaValue {
fn from(value: bool) -> Self {
if value {
Self::True
} else {
Self::False
}
}
}
impl From<String> for LuaValue {
fn from(value: String) -> Self {
Self::String(value.into_bytes())
}
}
impl From<&str> for LuaValue {
fn from(value: &str) -> Self {
Self::String(value.as_bytes().to_vec())
}
}
impl From<Vec<u8>> for LuaValue {
fn from(value: Vec<u8>) -> Self {
Self::String(value)
}
}
impl From<&[u8]> for LuaValue {
fn from(value: &[u8]) -> Self {
Self::String(value.to_vec())
}
}
impl<const N: usize> From<&[u8; N]> for LuaValue {
fn from(value: &[u8; N]) -> Self {
Self::String(value.to_vec())
}
}
impl From<f64> for LuaValue {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn unknown_lua_value_is_truthy_returns_none() {
assert!(LuaValue::Unknown.is_truthy().is_none());
}
#[test]
fn false_value_is_not_truthy() {
assert!(!LuaValue::False.is_truthy().unwrap());
}
#[test]
fn nil_value_is_not_truthy() {
assert!(!LuaValue::Nil.is_truthy().unwrap());
}
#[test]
fn true_value_is_truthy() {
assert!(LuaValue::True.is_truthy().unwrap());
}
#[test]
fn zero_value_is_truthy() {
assert!(LuaValue::Number(0_f64).is_truthy().unwrap());
}
#[test]
fn string_value_is_truthy() {
assert!(LuaValue::String(b"".to_vec()).is_truthy().unwrap());
}
#[test]
fn table_value_is_truthy() {
assert!(LuaValue::Table.is_truthy().unwrap());
}
mod number_coercion {
use super::*;
macro_rules! number_coercion {
($($name:ident ($string:literal) => $result:expr),*) => {
$(
#[test]
fn $name() {
assert_eq!(
LuaValue::String($string.into()).number_coercion(),
LuaValue::Number($result)
);
}
)*
};
}
macro_rules! no_number_coercion {
($($name:ident ($string:literal)),*) => {
$(
#[test]
fn $name() {
assert_eq!(
LuaValue::String($string.into()).number_coercion(),
LuaValue::String($string.into())
);
}
)*
};
}
number_coercion!(
zero("0") => 0.0,
integer("12") => 12.0,
integer_with_leading_zeros("00012") => 12.0,
integer_with_ending_space("12 ") => 12.0,
integer_with_leading_space(" 123") => 123.0,
integer_with_leading_tab("\t123") => 123.0,
negative_integer("-3") => -3.0,
hex_zero("0x0") => 0.0,
hex_integer("0xA") => 10.0,
negative_hex_integer("-0xA") => -10.0,
float("0.5") => 0.5,
negative_float("-0.5") => -0.5,
float_starting_with_dot(".5") => 0.5
);
no_number_coercion!(
letter_suffix("123a"),
hex_prefix("0x"),
space_between_minus("- 1"),
two_seperated_digits(" 1 2")
);
}
}